This file is used to combine three datasets:

  • our dataset with 5 HS patients and 2 healthy donors
  • Wu dataset with 6 samples from 4 healthy donors
  • Takahashi dataset with 5 samples

We load each individual sample, remove melanocytes, and merge the remaining cells.

library(dplyr)
library(patchwork)
library(ggplot2)
library(ComplexHeatmap)

.libPaths()
## [1] "/usr/local/lib/R/library"

Preparation

In this section, we set the global settings of the analysis. We will store data there :

save_name = "data3"
out_dir = "."
n_threads = 5 # for tSNE

We combine the three sample information :

sample_info_1 = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_sample_info.rds"))
sample_info_2 = readRDS(paste0(out_dir, "/../5_wu/1_metadata/wu_sample_info.rds"))
sample_info_3 = readRDS(paste0(out_dir, "/../6_takahashi/1_metadata/takahashi_sample_info.rds"))

column_to_keep = c("project_name", "sample_type", "sample_identifier",
                   "platform", "gender", "location", "laboratory", "color")

sample_info = rbind.data.frame(sample_info_1[, column_to_keep],
                               sample_info_2[, column_to_keep],
                               sample_info_3[, column_to_keep],
                               stringsAsFactors = FALSE)

graphics::pie(rep(1, nrow(sample_info)),
              col = sample_info$color,
              labels = sample_info$project_name)

Here are custom colors for each cell type :

color_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_color_markers.rds"))

data.frame(cell_type = names(color_markers),
           color = unlist(color_markers)) %>%
  ggplot2::ggplot(., aes(x = cell_type, y = 0, fill = cell_type)) +
  ggplot2::geom_point(pch = 21, size = 5) +
  ggplot2::scale_fill_manual(values = unlist(color_markers), breaks = names(color_markers)) +
  ggplot2::theme_classic() +
  ggplot2::theme(legend.position = "none",
                 axis.line = element_blank(),
                 axis.title = element_blank(),
                 axis.ticks = element_blank(),
                 axis.text.y = element_blank(),
                 axis.text.x = element_text(angle = 30, hjust = 1))

We load the markers and specific colors for each cell type :

cell_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_cell_markers.rds"))
lengths(cell_markers)
##          CD4 T cells          CD8 T cells     Langerhans cells 
##                   13                   13                    9 
##          macrophages              B cells              cuticle 
##                   10                   16                   15 
##               cortex              medulla                  IRS 
##                   16                   10                   16 
##        proliferative               HF-SCs            IFE basal 
##                   20                   17                   16 
## IFE granular spinous                  ORS          melanocytes 
##                   17                   15                   10 
##            sebocytes 
##                    8

We load markers to display on the dotplot :

dotplot_markers = readRDS(paste0(out_dir, "/../1_metadata/hs_hd_dotplot_markers.rds"))
dotplot_markers
## $`CD4 T cells`
## [1] "PTPRC" "CD3E"  "CD4"  
## 
## $`CD8 T cells`
## [1] "CD3E" "CD8A"
## 
## $`Langerhans cells`
## [1] "CD207" "CPVL" 
## 
## $macrophages
## [1] "TREM2" "MSR1" 
## 
## $`B cells`
## [1] "CD79A" "CD79B"
## 
## $cuticle
## [1] "MSX2"  "KRT32" "KRT35"
## 
## $cortex
## [1] "KRT31" "PRR9" 
## 
## $medulla
## [1] "BAMBI"   "ADLH1A3"
## 
## $IRS
## [1] "KRT71" "KRT73"
## 
## $proliferative
## [1] "TOP2A" "MCM5"  "TK1"  
## 
## $`HF-SCs`
## [1] "KRT14"  "CXCL14"
## 
## $`IFE basal`
## [1] "COL17A1" "KRT15"  
## 
## $`IFE granular spinous`
## [1] "SPINK5" "KRT1"  
## 
## $ORS
## [1] "KRT16" "KRT6C"
## 
## $melanocytes
## [1] "DCT"   "MLANA"
## 
## $sebocytes
## [1] "CLMP"  "PPARG"

Make data3 dataset

Individual datasets

For each sample, we :

  • load individual dataset
  • look at cell annotation

We load individual datasets :

sobj_list = list()

# Our data
project_names_oi = sample_info_1$project_name
sobj_list[["here"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["here"]]) = project_names_oi

# Wu data
project_names_oi = sample_info_2$project_name
sobj_list[["wu"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../5_wu/2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["wu"]]) = project_names_oi

# Takahashi data
project_names_oi = sample_info_3$project_name
sobj_list[["takahashi"]] = lapply(project_names_oi, FUN = function(one_project_name) {
  subsobj = readRDS(paste0(out_dir, "/../6_takahashi/2_individual/datasets/",
                           one_project_name, "_sobj_filtered.rds"))
  return(subsobj)
})
names(sobj_list[["takahashi"]]) = project_names_oi

# Unlist
sobj_list = unlist(sobj_list, recursive = FALSE)

lapply(sobj_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  rbind(., colSums(.))
##                        [,1]  [,2]
## here.2021_31          27955  1043
## here.2021_36          27955   602
## here.2021_41          27955  2256
## here.2022_03          27955  3977
## here.2022_14          27955  2588
## here.2022_01          27955  1213
## here.2022_02          27955  2286
## wu.F18                27955  1372
## wu.F31B               27955  4786
## wu.F31W               27955  3520
## wu.F59                27955  2445
## wu.F62B               27955  3279
## wu.F62W               27955  2360
## takahashi.GSM3717034  10320    71
## takahashi.GSM3717035  12129   275
## takahashi.GSM3717036  14170   510
## takahashi.GSM3717037  32458  4084
## takahashi.GSM3717038  32458  1094
##                      464950 37761

We represent cells in the tSNE :

name2D = "RNA_pca_20_tsne"

We look at cell type annotation for each dataset :

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  mytitle = as.character(unique(one_sobj$project_name))
  mysubtitle = ncol(one_sobj)
  
  if (!(name2D %in% names(one_sobj@reductions))) {
    name2D = names(one_sobj@reductions)[2]
  }
  
  p = Seurat::DimPlot(one_sobj, group.by = "cell_type",
                      reduction = name2D) +
    ggplot2::scale_color_manual(values = color_markers,
                                breaks = names(color_markers),
                                name = "Cell Type") +
    ggplot2::labs(title = mytitle,
                  subtitle = paste0(mysubtitle, " cells")) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5)) +
    Seurat::NoAxes()
  
  return(p)
})

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, ncol = 4) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

and clustering :

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  mytitle = as.character(unique(one_sobj$project_name))
  mysubtitle = ncol(one_sobj)
  
  if (!(name2D %in% names(one_sobj@reductions))) {
    name2D = names(one_sobj@reductions)[2]
  }
  
  p = Seurat::DimPlot(one_sobj, group.by = "seurat_clusters",
                      reduction = name2D, label = TRUE) +
    ggplot2::labs(title = mytitle,
                  subtitle = paste0(mysubtitle, " cells")) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5)) +
    Seurat::NoAxes() + Seurat::NoLegend()
  
  return(p)
})

patchwork::wrap_plots(plot_list, ncol = 4)

Melanocytes removal

For each individual dataset, we remove melanocytes. First, we smooth cell type annotation at a cluster level :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  cluster_type = table(one_sobj$cell_type, one_sobj$seurat_clusters) %>%
    prop.table(., margin = 2) %>%
    apply(., 2, which.max)
  cluster_type = setNames(nm = names(cluster_type),
                          levels(one_sobj$cell_type)[cluster_type])
  
  one_sobj$cluster_type = cluster_type[one_sobj$seurat_clusters]
  
  ## Output
  return(one_sobj)
})

To locate melanocytes, we look at their score, cell type annotation, and clustering.

plot_list = lapply(sobj_list, FUN = function(one_sobj) {
  project_name = as.character(unique(one_sobj$project_name))
  plot_sublist = list()
  
  if (!(name2D %in% names(one_sobj@reductions))) {
    name2D = names(one_sobj@reductions)[2]
  }
  
  # Score
  plot_sublist[[1]] = Seurat::FeaturePlot(one_sobj, reduction = name2D,
                                          features = "score_melanocytes") +
    ggplot2::labs(title = project_name,
                  subtitle = "Melanocytes score") +
    Seurat::NoAxes() +
    ggplot2::scale_color_gradientn(colors = aquarius:::color_gene) +
    ggplot2::theme(aspect.ratio = 1,
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Cell type
  plot_sublist[[2]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "cell_type",
                                      order = "melanocytes") +
    ggplot2::scale_color_manual(values = c("purple", rep("gray92", length(color_markers) - 1)),
                                breaks = c("melanocytes", setdiff(names(color_markers), "melanocytes"))) +
    ggplot2::labs(title = "Cell type annotation",
                  subtitle = paste0(sum(one_sobj$cell_type == "melanocytes"),
                                    " melanocytes")) +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Clusters
  plot_sublist[[3]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "seurat_clusters",
                                      label = TRUE) +
    ggplot2::labs(title = "Clusters") +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  # Cluster type
  plot_sublist[[4]] = Seurat::DimPlot(one_sobj,
                                      reduction = name2D,
                                      group.by = "cluster_type") +
    ggplot2::scale_color_manual(values = c("purple", rep("gray92", length(color_markers) - 1)),
                                breaks = c("melanocytes", setdiff(names(color_markers), "melanocytes"))) +
    ggplot2::labs(title = "Cluster annotation",
                  subtitle = paste0(sum(one_sobj$cluster_type == "melanocytes"),
                                    " melanocytes")) +
    Seurat::NoAxes() + Seurat::NoLegend() +
    ggplot2::theme(aspect.ratio = 1,
                   plot.title = element_text(hjust = 0.5),
                   plot.subtitle = element_text(hjust = 0.5))
  
  return(plot_sublist)
}) %>% unlist(., recursive = FALSE)

patchwork::wrap_plots(plot_list, ncol = 4)

We remove melanocytes based on cluster annotation for 10X datasets and based on the cell type annotation for Drop-Seq datasets :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  if (one_sobj@project.name %in% c("GSM3717034", "GSM3717035", "GSM3717036")) {
    one_sobj$is_of_interest = (one_sobj$cell_type != "melanocytes")
  } else {
    one_sobj$is_of_interest = (one_sobj$cluster_type != "melanocytes")
  }
  
  if (sum(one_sobj$is_of_interest) > 0) {
    one_sobj = subset(one_sobj, is_of_interest == TRUE)
  } else {
    one_sobj = NA
  }
  
  one_sobj$is_of_interest = NULL
  return(one_sobj)
})

lapply(sobj_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  rbind(., colSums(.))
##                        [,1]  [,2]
## here.2021_31          27955   885
## here.2021_36          27955   528
## here.2021_41          27955  1982
## here.2022_03          27955  3457
## here.2022_14          27955  2422
## here.2022_01          27955   835
## here.2022_02          27955  2002
## wu.F18                27955  1372
## wu.F31B               27955  4624
## wu.F31W               27955  3520
## wu.F59                27955  2445
## wu.F62B               27955  3221
## wu.F62W               27955  2360
## takahashi.GSM3717034  10320    65
## takahashi.GSM3717035  12129   270
## takahashi.GSM3717036  14170   497
## takahashi.GSM3717037  32458  3781
## takahashi.GSM3717038  32458  1023
##                      464950 35289

Re-annotation

We remove melanocytes from annotation :

cell_markers = cell_markers[names(cell_markers) != "melanocytes"]
color_markers = color_markers[names(color_markers) != "melanocytes"]
dotplot_markers = dotplot_markers[names(dotplot_markers) != "melanocytes"]

We re-annotate cells for cell type, since melanocytes have been removed :

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  # Remove old annotation
  one_sobj@meta.data[, grep(colnames(one_sobj@meta.data), pattern = "score", value = TRUE)] = NULL
  
  # Re-annot
  one_sobj = aquarius::cell_annot_custom(one_sobj,
                                         newname = "cell_type",
                                         markers = cell_markers,
                                         use_negative = TRUE,
                                         add_score = FALSE,
                                         verbose = TRUE)
  
  # Set factor levels
  one_sobj$cell_type = factor(one_sobj$cell_type, levels = names(cell_markers))
  
  return(one_sobj)
})

Gene annotation

Our dataset and Wu dataset were processed using the same annotation. In Takahashi dataset, all genes are not shared across datasets:

Note: With the ggvenn package, this is not possible to make a Venn diagram with 5 sets.

ggvenn::ggvenn(data = list(
  here.2021_31 = sobj_list[["here.2021_31"]]@assays[["RNA"]]@meta.features$Ensembl_ID,
  wu.F18 = sobj_list[["wu.F18"]]@assays[["RNA"]]@meta.features$Ensembl_ID,
  takahashi.GSM3717034 = sobj_list[["takahashi.GSM3717034"]]@assays[["RNA"]]@meta.features$Ensembl_ID,
  takahashi.GSM3717038 = sobj_list[["takahashi.GSM3717038"]]@assays[["RNA"]]@meta.features$Ensembl_ID),
  stroke_size = 0.5, set_name_size = 4) +
  ggplot2::labs(title = "Gene Ensembl IDs between the 4 datasets") +
  ggplot2::theme(plot.title = element_text(hjust = 0.5, face = "bold"))

We keep common genes between all datasets + common genes between the 10X datasets, based on the EnsemblID

# All Ensembl IDs
common_genes = lapply(sobj_list, FUN = function(one_sobj) {
  ensembl_id = one_sobj@assays[["RNA"]]@meta.features$Ensembl_ID
  
  return(ensembl_id)
})
names(common_genes) = names(sobj_list)

# Common between 10X datasets
common_genes_10x = common_genes[!(names(common_genes) %in% c("takahashi.GSM3717034",
                                                             "takahashi.GSM3717035",
                                                             "takahashi.GSM3717036"))] %>%
  Reduce(intersect, .)

# Common between all
common_genes = Reduce(intersect, common_genes)

# Venn diagram
ggvenn::ggvenn(data = list(
  common_all = common_genes,
  common_10X = common_genes_10x),
  stroke_size = 0.5, set_name_size = 4) +
  ggplot2::labs(title = "Gene Ensembl IDs") +
  ggplot2::theme(plot.title = element_text(hjust = 0.5, face = "bold"))

We keep the union of all these genes :

common_genes = union(common_genes, common_genes_10x)
rm(common_genes_10x)

length(common_genes)
## [1] 25605

To which gene names they correspond, in one of our dataset ?

gene_corresp = sobj_list[["here.2021_31"]]@assays$RNA@meta.features %>%
  dplyr::filter(Ensembl_ID %in% common_genes) %>%
  dplyr::select(Ensembl_ID, gene_name)

dim(gene_corresp)
## [1] 25605     2
head(gene_corresp)
##                  Ensembl_ID   gene_name
## MIR1302-2HG ENSG00000243485 MIR1302-2HG
## FAM138A     ENSG00000237613     FAM138A
## OR4F5       ENSG00000186092       OR4F5
## AL627309.1  ENSG00000238009  AL627309.1
## AL627309.3  ENSG00000239945  AL627309.3
## AL627309.4  ENSG00000241599  AL627309.4

We subset Seurat object for the Ensembl IDs of interest.

sobj_list = lapply(sobj_list, FUN = function(one_sobj) {
  # Extract metadata
  one_metadata = one_sobj@meta.data
  
  # Extract and subset gene annotation
  one_annotation = one_sobj@assays[["RNA"]]@meta.features %>%
    dplyr::filter(Ensembl_ID %in% gene_corresp$Ensembl_ID)
  
  # Subset gene corresp for reordering
  one_gene_corresp = gene_corresp %>%
    dplyr::filter(Ensembl_ID %in% one_annotation$Ensembl_ID)
  
  # Extract count matrix and subset genes
  one_count_matrix = one_sobj@assays[["RNA"]]@counts
  one_count_matrix = one_count_matrix[rownames(one_annotation), ]
  
  # Reorder according to the gene correspondence
  gene_order = match(one_gene_corresp$Ensembl_ID,
                     one_annotation$Ensembl_ID)
  
  # Reorder the count matrix and annotation
  one_annotation = one_annotation[gene_order, ]
  one_count_matrix = one_count_matrix[gene_order, ]
  rownames(one_count_matrix) = rownames(one_gene_corresp)
  
  # Build again the Seurat object
  one_sobj = Seurat::CreateSeuratObject(counts = one_count_matrix,
                                        meta.data = one_metadata)
  one_sobj@assays[["RNA"]]@meta.features = one_gene_corresp
  
  return(one_sobj)
})

lapply(sobj_list, FUN = dim) %>%
  do.call(rbind, .) %>%
  rbind(., colSums(.))
##                        [,1]  [,2]
## here.2021_31          25605   885
## here.2021_36          25605   528
## here.2021_41          25605  1982
## here.2022_03          25605  3457
## here.2022_14          25605  2422
## here.2022_01          25605   835
## here.2022_02          25605  2002
## wu.F18                25605  1372
## wu.F31B               25605  4624
## wu.F31W               25605  3520
## wu.F59                25605  2445
## wu.F62B               25605  3221
## wu.F62W               25605  2360
## takahashi.GSM3717034   8926    65
## takahashi.GSM3717035  10676   270
## takahashi.GSM3717036  11842   497
## takahashi.GSM3717037  25605  3781
## takahashi.GSM3717038  25605  1023
##                      415519 35289

Combined dataset

We combine all datasets :

sobj = base::merge(sobj_list[[1]],
                   y = sobj_list[c(2:length(sobj_list))],
                   add.cell.ids = names(sobj_list))
sobj
## An object of class Seurat 
## 25605 features across 35289 samples within 1 assay 
## Active assay: RNA (25605 features, 0 variable features)

We add again the correspondence between gene names and gene ID. We take the correspondence from one individual 10X dataset.

sobj@assays$RNA@meta.features = sobj_list[[1]]@assays$RNA@meta.features[, c("Ensembl_ID", "gene_name")]

head(sobj@assays$RNA@meta.features)
##                  Ensembl_ID   gene_name
## MIR1302-2HG ENSG00000243485 MIR1302-2HG
## FAM138A     ENSG00000237613     FAM138A
## OR4F5       ENSG00000186092       OR4F5
## AL627309.1  ENSG00000238009  AL627309.1
## AL627309.3  ENSG00000239945  AL627309.3
## AL627309.4  ENSG00000241599  AL627309.4

We remove the list of objects :

rm(sobj_list)

We keep a subset of meta.data and reset levels :

sobj@meta.data = sobj@meta.data[, c("orig.ident", "nCount_RNA", "nFeature_RNA", "log_nCount_RNA",
                                    "project_name", "sample_identifier", "sample_type",
                                    "laboratory", "location", "Seurat.Phase", "cyclone.Phase",
                                    "percent.mt", "percent.rb", "cell_type")]

sobj$orig.ident = factor(sobj$orig.ident, levels = levels(sample_info$project_name))
sobj$project_name = factor(sobj$project_name, levels = levels(sample_info$project_name))
sobj$sample_identifier = factor(sobj$sample_identifier, levels = levels(sample_info$sample_identifier))
sobj$sample_type = factor(sobj$sample_type, levels = unique(sample_info$sample_type))
sobj$cell_type = factor(sobj$cell_type, levels = names(color_markers))

summary(sobj@meta.data)
##       orig.ident      nCount_RNA      nFeature_RNA  log_nCount_RNA  
##  F31B      : 4624   Min.   :   318   Min.   : 250   Min.   : 5.762  
##  GSM3717037: 3781   1st Qu.:  3274   1st Qu.:1226   1st Qu.: 8.094  
##  F31W      : 3520   Median :  8707   Median :2298   Median : 9.072  
##  2022_03   : 3457   Mean   : 11627   Mean   :2438   Mean   : 8.877  
##  F62B      : 3221   3rd Qu.: 16354   3rd Qu.:3371   3rd Qu.: 9.702  
##  F59       : 2445   Max.   :139803   Max.   :7942   Max.   :11.848  
##  (Other)   :14241                                                   
##      project_name        sample_identifier sample_type  laboratory       
##  F31B      : 4624   Wu_HD_2       : 4624   HS: 9274    Length:35289      
##  GSM3717037: 3781   Takahashi_HD_4: 3781   HD:26015    Class :character  
##  F31W      : 3520   Wu_HD_3       : 3520               Mode  :character  
##  2022_03   : 3457   HS_4          : 3457                                 
##  F62B      : 3221   Wu_HD_5       : 3221                                 
##  F59       : 2445   Wu_HD_4       : 2445                                 
##  (Other)   :14241   (Other)       :14241                                 
##    location         Seurat.Phase       cyclone.Phase        percent.mt    
##  Length:35289       Length:35289       Length:35289       Min.   : 0.000  
##  Class :character   Class :character   Class :character   1st Qu.: 2.778  
##  Mode  :character   Mode  :character   Mode  :character   Median : 4.516  
##                                                           Mean   : 5.360  
##                                                           3rd Qu.: 6.868  
##                                                           Max.   :20.000  
##                                                                           
##    percent.rb                     cell_type   
##  Min.   : 0.4948   ORS                 :9121  
##  1st Qu.:19.1530   IFE granular spinous:5349  
##  Median :23.9507   IFE basal           :4117  
##  Mean   :23.2444   HF-SCs              :3292  
##  3rd Qu.:28.2717   cuticle             :3021  
##  Max.   :48.0392   proliferative       :2590  
##                    (Other)             :7799

Processing

We remove genes that are expressed in less than 5 cells :

sobj = aquarius::filter_features(sobj, min_cells = 5)
## [1] 25605 35289
## [1] 19849 35289
sobj
## An object of class Seurat 
## 19849 features across 35289 samples within 1 assay 
## Active assay: RNA (19849 features, 0 variable features)

Metadata

How many cells by sample ?

table(sobj$project_name)
## 
##    2021_31    2021_36    2021_41    2022_03    2022_14    2022_01    2022_02 
##        885        528       1982       3457       2422        835       2002 
##        F18       F31B       F31W        F59       F62B       F62W GSM3717034 
##       1372       4624       3520       2445       3221       2360         65 
## GSM3717035 GSM3717036 GSM3717037 GSM3717038 
##        270        497       3781       1023

We represent this information as a barplot :

aquarius::plot_barplot(df = table(sobj$project_name,
                                  sobj$cell_type) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("Sample", "Cell Type", "Number")),
                       x = "Sample", y = "Number", fill = "Cell Type",
                       position = position_fill()) +
  ggplot2::scale_fill_manual(values = unlist(color_markers),
                             breaks = names(color_markers),
                             name = "Cell Type")

This is the same barplot with another position :

aquarius::plot_barplot(df = table(sobj$project_name,
                                  sobj$cell_type) %>%
                         as.data.frame.table() %>%
                         `colnames<-`(c("Sample", "Cell Type", "Number")),
                       x = "Sample", y = "Number", fill = "Cell Type",
                       position = position_stack()) +
  ggplot2::scale_fill_manual(values = unlist(color_markers),
                             breaks = names(color_markers),
                             name = "Cell Type")

Projection

We normalize the count matrix for remaining cells and select highly variable features :

sobj = Seurat::NormalizeData(sobj,
                             normalization.method = "LogNormalize")
sobj = Seurat::FindVariableFeatures(sobj, nfeatures = 2000)
sobj = Seurat::ScaleData(sobj)

sobj
## An object of class Seurat 
## 19849 features across 35289 samples within 1 assay 
## Active assay: RNA (19849 features, 2000 variable features)

We perform a PCA :

sobj = Seurat::RunPCA(sobj,
                      assay = "RNA",
                      reduction.name = "RNA_pca",
                      npcs = 100,
                      seed.use = 1337L)
sobj
## An object of class Seurat 
## 19849 features across 35289 samples within 1 assay 
## Active assay: RNA (19849 features, 2000 variable features)
##  1 dimensional reduction calculated: RNA_pca

We choose the number of dimensions such that they summarize 60 % of the variability :

stdev = sobj@reductions[["RNA_pca"]]@stdev
stdev_prop = cumsum(stdev)/sum(stdev)
ndims = which(stdev_prop > 0.60)[1]
ndims
## [1] 37

We can visualize this on the elbow plot :

elbow_p = Seurat::ElbowPlot(sobj, ndims = 100, reduction = "RNA_pca") +
  ggplot2::geom_point(x = ndims, y = stdev[ndims], col = "red")
x_text = ggplot_build(elbow_p)$layout$panel_params[[1]]$x$get_labels() %>% as.numeric()
elbow_p = elbow_p +
  ggplot2::scale_x_continuous(breaks = sort(c(x_text, ndims)), limits = c(0, 100))
x_color = ifelse(ggplot_build(elbow_p)$layout$panel_params[[1]]$x$get_labels() %>%
                   as.numeric() %>% round(., 2) == round(ndims, 2), "red", "black")
elbow_p = elbow_p +
  ggplot2::theme_classic() +
  ggplot2::theme(axis.text.x = element_text(color = x_color))

elbow_p

We generate a tSNE and a UMAP with 37 principal components :

sobj = Seurat::RunTSNE(sobj,
                       reduction = "RNA_pca",
                       dims = 1:ndims,
                       seed.use = 1337L,
                       num_threads = n_threads, # Rtsne::Rtsne option
                       reduction.name = paste0("RNA_pca_", ndims, "_tsne"))

sobj = Seurat::RunUMAP(sobj,
                       reduction = "RNA_pca",
                       dims = 1:ndims,
                       seed.use = 1337L,
                       reduction.name = paste0("RNA_pca_", ndims, "_umap"))

(Time to run : 124.05 s)

We can visualize the two representations :

tsne = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("RNA_pca_", ndims, "_tsne")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - tSNE") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5),
                 legend.position = "none")

umap = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("RNA_pca_", ndims, "_umap")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - UMAP") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

tsne | umap

Batch-effect correction

We remove sample specific effect on the pca using Harmony :

`%||%` = function(lhs, rhs) {
  if (!is.null(x = lhs)) {
    return(lhs)
  } else {
    return(rhs)
  }
}

set.seed(1337L)
sobj = harmony::RunHarmony(object = sobj,
                           group.by.vars = "project_name",
                           plot_convergence = TRUE,
                           reduction = "RNA_pca",
                           assay.use = "RNA",
                           reduction.save = "harmony",
                           max.iter.harmony = 50,
                           project.dim = FALSE)

(Time to run : 198.87 s)

From this batch-effect removed projection, we generate a tSNE and a UMAP.

sobj = Seurat::RunUMAP(sobj, 
                       seed.use = 1337L,
                       dims = 1:ndims,
                       reduction = "harmony",
                       reduction.name = paste0("harmony_", ndims, "_umap"),
                       reduction.key = paste0("harmony_", ndims, "umap_"))

sobj = Seurat::RunTSNE(sobj,
                       dims = 1:ndims,
                       seed.use = 1337L,
                       num_threads = n_threads, # Rtsne::Rtsne option
                       reduction = "harmony",
                       reduction.name = paste0("harmony_", ndims, "_tsne"),
                       reduction.key = paste0("harmony", ndims, "tsne_"))

(Time to run : 127.66 s)

We visualize the corrected projections :

tsne = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("harmony_", ndims, "_tsne")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - harmony - tSNE") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5),
                 legend.position = "none")

umap = Seurat::DimPlot(sobj, group.by = "project_name",
                       reduction = paste0("harmony_", ndims, "_umap")) +
  ggplot2::scale_color_manual(values = sample_info$color,
                              breaks = sample_info$project_name) +
  Seurat::NoAxes() + ggplot2::ggtitle("PCA - harmony - UMAP") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

tsne | umap

We will keep the tSNE from harmony :

reduction = "harmony"
name2D = paste0("harmony_", ndims, "_tsne")

Clustering

We generate a clustering :

sobj = Seurat::FindNeighbors(sobj, reduction = reduction, dims = 1:ndims)
sobj = Seurat::FindClusters(sobj, resolution = 1.2)
## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
## 
## Number of nodes: 35289
## Number of edges: 1441587
## 
## Running Louvain algorithm...
## Maximum modularity in 10 random starts: 0.8872
## Number of communities: 33
## Elapsed time: 8 seconds
clusters_plot = Seurat::DimPlot(sobj, reduction = name2D, label = TRUE) +
  Seurat::NoAxes() + Seurat::NoLegend() +
  ggplot2::labs(title = "Clusters ID") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))
clusters_plot

Visualization

We represent the 4 quality metrics :

plot_list = Seurat::FeaturePlot(sobj, reduction = name2D,
                                combine = FALSE, pt.size = 0.25,
                                features = c("percent.mt", "percent.rb", "log_nCount_RNA", "nFeature_RNA"))
plot_list = lapply(plot_list, FUN = function(one_plot) {
  one_plot +
    Seurat::NoAxes() +
    ggplot2::scale_color_gradientn(colors = aquarius:::color_gene) +
    ggplot2::theme(aspect.ratio = 1)
})

patchwork::wrap_plots(plot_list, nrow = 1)

Cell type

We visualize cell type :

plot_list = lapply((c(paste0("RNA_pca_", ndims, "_tsne"),
                      paste0("RNA_pca_", ndims, "_umap"),
                      paste0("harmony_", ndims, "_tsne"),
                      paste0("harmony_", ndims, "_umap"))),
                   FUN = function(one_red) {
                     Seurat::DimPlot(sobj, group.by = "cell_type",
                                     reduction = one_red,
                                     cols = color_markers) +
                       Seurat::NoAxes() + ggplot2::ggtitle(one_red) +
                       ggplot2::theme(aspect.ratio = 1,
                                      plot.title = element_text(hjust = 0.5))
                   })

patchwork::wrap_plots(plot_list, nrow = 2) +
  patchwork::plot_layout(guides = "collect")

We make a representation split by origin to show cell types :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_by = "cell_type",
                                        group_color = color_markers)

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, ncol = 4) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

Laboratory

We can represent cell type split by laboratory, split by sample of origin :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "laboratory",
                                        group_by = "cell_type",
                                        group_color = color_markers)

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, nrow = 1) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

Location

We can represent cell type split by location, split by sample of origin :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "location",
                                        group_by = "cell_type",
                                        group_color = color_markers)

plot_list[[length(plot_list) + 1]] = patchwork::guide_area()

patchwork::wrap_plots(plot_list, nrow = 1) +
  patchwork::plot_layout(guides = "collect") &
  ggplot2::theme(legend.position = "right")

Clusters

We can represent clusters, split by sample of origin :

plot_list = aquarius::plot_split_dimred(sobj,
                                        reduction = name2D,
                                        split_by = "project_name",
                                        group_by = "seurat_clusters",
                                        split_color = setNames(sample_info$color,
                                                               nm = sample_info$project_name),
                                        group_color = aquarius::gg_color_hue(length(levels(sobj$seurat_clusters))))

plot_list[[length(plot_list) + 1]] = clusters_plot +
  ggplot2::labs(title = "Cluster ID") &
  ggplot2::theme(plot.title = element_text(hjust = 0.5, size = 15))

patchwork::wrap_plots(plot_list, ncol = 4) &
  Seurat::NoLegend()

Cell cycle

We visualize cell cycle annotation, and BIRC5 and TOP2A expression levels :

plot_list = list()

# Seurat
plot_list[[1]] = Seurat::DimPlot(sobj, group.by = "Seurat.Phase",
                                 reduction = name2D) +
  Seurat::NoAxes() + ggplot2::labs(title = "Seurat annotation") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# cyclone
plot_list[[2]] = Seurat::DimPlot(sobj, group.by = "cyclone.Phase",
                                 reduction = name2D) +
  Seurat::NoAxes() + ggplot2::labs(title = "cyclone annotation") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# BIRC5
plot_list[[3]] = Seurat::FeaturePlot(sobj, features = "BIRC5",
                                     reduction = name2D) +
  ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

# TK1
plot_list[[4]] = Seurat::FeaturePlot(sobj, features = "TOP2A",
                                     reduction = name2D) +
  ggplot2::scale_color_gradientn(colors = aquarius::color_gene) +
  Seurat::NoAxes() +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

patchwork::wrap_plots(plot_list, ncol = 2)

Save

We save the Seurat object :

saveRDS(sobj, file = paste0(out_dir, "/", save_name, "_sobj.rds"))

R Session

show
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.6 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so
## LAPACK: /usr/local/lib/R/lib/libRlapack.so
## 
## locale:
## [1] C
## 
## attached base packages:
## [1] grid      stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
## [1] ComplexHeatmap_2.14.0 ggplot2_3.3.5         patchwork_1.1.2      
## [4] dplyr_1.0.7          
## 
## loaded via a namespace (and not attached):
##   [1] softImpute_1.4              graphlayouts_0.7.0         
##   [3] pbapply_1.4-2               lattice_0.20-41            
##   [5] haven_2.3.1                 vctrs_0.3.8                
##   [7] usethis_2.0.1               dynwrap_1.2.1              
##   [9] blob_1.2.1                  survival_3.2-13            
##  [11] prodlim_2019.11.13          dynutils_1.0.5             
##  [13] later_1.3.0                 DBI_1.1.1                  
##  [15] R.utils_2.11.0              SingleCellExperiment_1.8.0 
##  [17] rappdirs_0.3.3              uwot_0.1.8                 
##  [19] dqrng_0.2.1                 jpeg_0.1-8.1               
##  [21] zlibbioc_1.32.0             pspline_1.0-18             
##  [23] pcaMethods_1.78.0           mvtnorm_1.1-1              
##  [25] htmlwidgets_1.5.4           GlobalOptions_0.1.2        
##  [27] future_1.22.1               UpSetR_1.4.0               
##  [29] laeken_0.5.2                leiden_0.3.3               
##  [31] clustree_0.4.3              parallel_3.6.3             
##  [33] scater_1.14.6               irlba_2.3.3                
##  [35] DEoptimR_1.0-9              tidygraph_1.1.2            
##  [37] Rcpp_1.0.9                  readr_2.0.2                
##  [39] KernSmooth_2.23-17          carrier_0.1.0              
##  [41] promises_1.1.0              gdata_2.18.0               
##  [43] DelayedArray_0.12.3         limma_3.42.2               
##  [45] graph_1.64.0                RcppParallel_5.1.4         
##  [47] Hmisc_4.4-0                 fs_1.5.2                   
##  [49] RSpectra_0.16-0             fastmatch_1.1-0            
##  [51] ranger_0.12.1               digest_0.6.25              
##  [53] png_0.1-7                   sctransform_0.2.1          
##  [55] cowplot_1.0.0               DOSE_3.12.0                
##  [57] ggvenn_0.1.9                here_1.0.1                 
##  [59] TInGa_0.0.0.9000            ggraph_2.0.3               
##  [61] pkgconfig_2.0.3             GO.db_3.10.0               
##  [63] DelayedMatrixStats_1.8.0    gower_0.2.1                
##  [65] ggbeeswarm_0.6.0            iterators_1.0.12           
##  [67] DropletUtils_1.6.1          reticulate_1.26            
##  [69] clusterProfiler_3.14.3      SummarizedExperiment_1.16.1
##  [71] circlize_0.4.15             beeswarm_0.4.0             
##  [73] GetoptLong_1.0.5            xfun_0.35                  
##  [75] bslib_0.3.1                 zoo_1.8-10                 
##  [77] tidyselect_1.1.0            reshape2_1.4.4             
##  [79] purrr_0.3.4                 ica_1.0-2                  
##  [81] pcaPP_1.9-73                viridisLite_0.3.0          
##  [83] rtracklayer_1.46.0          rlang_1.0.2                
##  [85] hexbin_1.28.1               jquerylib_0.1.4            
##  [87] dyneval_0.9.9               glue_1.4.2                 
##  [89] RColorBrewer_1.1-2          matrixStats_0.56.0         
##  [91] stringr_1.4.0               lava_1.6.7                 
##  [93] europepmc_0.3               DESeq2_1.26.0              
##  [95] recipes_0.1.17              labeling_0.3               
##  [97] harmony_0.1.0               httpuv_1.5.2               
##  [99] class_7.3-17                BiocNeighbors_1.4.2        
## [101] DO.db_2.9                   annotate_1.64.0            
## [103] jsonlite_1.7.2              XVector_0.26.0             
## [105] bit_4.0.4                   mime_0.9                   
## [107] aquarius_0.1.5              Rsamtools_2.2.3            
## [109] gridExtra_2.3               gplots_3.0.3               
## [111] stringi_1.4.6               processx_3.5.2             
## [113] gsl_2.1-6                   bitops_1.0-6               
## [115] cli_3.0.1                   batchelor_1.2.4            
## [117] RSQLite_2.2.0               randomForest_4.6-14        
## [119] tidyr_1.1.4                 data.table_1.14.2          
## [121] rstudioapi_0.13             org.Mm.eg.db_3.10.0        
## [123] GenomicAlignments_1.22.1    nlme_3.1-147               
## [125] qvalue_2.18.0               scran_1.14.6               
## [127] locfit_1.5-9.4              scDblFinder_1.1.8          
## [129] listenv_0.8.0               ggthemes_4.2.4             
## [131] gridGraphics_0.5-0          R.oo_1.24.0                
## [133] dbplyr_1.4.4                BiocGenerics_0.32.0        
## [135] TTR_0.24.2                  readxl_1.3.1               
## [137] lifecycle_1.0.1             timeDate_3043.102          
## [139] ggpattern_0.3.1             munsell_0.5.0              
## [141] cellranger_1.1.0            R.methodsS3_1.8.1          
## [143] proxyC_0.1.5                visNetwork_2.0.9           
## [145] caTools_1.18.0              codetools_0.2-16           
## [147] Biobase_2.46.0              GenomeInfoDb_1.22.1        
## [149] vipor_0.4.5                 lmtest_0.9-38              
## [151] msigdbr_7.5.1               htmlTable_1.13.3           
## [153] triebeard_0.3.0             lsei_1.2-0                 
## [155] xtable_1.8-4                ROCR_1.0-7                 
## [157] BiocManager_1.30.10         scatterplot3d_0.3-41       
## [159] abind_1.4-5                 farver_2.0.3               
## [161] parallelly_1.28.1           RANN_2.6.1                 
## [163] askpass_1.1                 GenomicRanges_1.38.0       
## [165] RcppAnnoy_0.0.16            tibble_3.1.5               
## [167] ggdendro_0.1-20             cluster_2.1.0              
## [169] future.apply_1.5.0          Seurat_3.1.5               
## [171] dendextend_1.15.1           Matrix_1.3-2               
## [173] ellipsis_0.3.2              prettyunits_1.1.1          
## [175] lubridate_1.7.9             ggridges_0.5.2             
## [177] igraph_1.2.5                RcppEigen_0.3.3.7.0        
## [179] fgsea_1.12.0                remotes_2.4.2              
## [181] scBFA_1.0.0                 destiny_3.0.1              
## [183] VIM_6.1.1                   testthat_3.1.0             
## [185] htmltools_0.5.2             BiocFileCache_1.10.2       
## [187] yaml_2.2.1                  utf8_1.1.4                 
## [189] plotly_4.9.2.1              XML_3.99-0.3               
## [191] ModelMetrics_1.2.2.2        e1071_1.7-3                
## [193] foreign_0.8-76              withr_2.5.0                
## [195] fitdistrplus_1.0-14         BiocParallel_1.20.1        
## [197] xgboost_1.4.1.1             bit64_4.0.5                
## [199] foreach_1.5.0               robustbase_0.93-9          
## [201] Biostrings_2.54.0           GOSemSim_2.13.1            
## [203] rsvd_1.0.3                  memoise_2.0.0              
## [205] evaluate_0.18               forcats_0.5.0              
## [207] rio_0.5.16                  geneplotter_1.64.0         
## [209] tzdb_0.1.2                  caret_6.0-86               
## [211] ps_1.6.0                    DiagrammeR_1.0.6.1         
## [213] curl_4.3                    fdrtool_1.2.15             
## [215] fansi_0.4.1                 highr_0.8                  
## [217] urltools_1.7.3              xts_0.12.1                 
## [219] GSEABase_1.48.0             acepack_1.4.1              
## [221] edgeR_3.28.1                checkmate_2.0.0            
## [223] scds_1.2.0                  cachem_1.0.6               
## [225] npsurv_0.4-0                babelgene_22.3             
## [227] rjson_0.2.20                openxlsx_4.1.5             
## [229] ggrepel_0.9.1               clue_0.3-60                
## [231] rprojroot_2.0.2             stabledist_0.7-1           
## [233] tools_3.6.3                 sass_0.4.0                 
## [235] nichenetr_1.1.1             magrittr_2.0.1             
## [237] RCurl_1.98-1.2              proxy_0.4-24               
## [239] car_3.0-11                  ape_5.3                    
## [241] ggplotify_0.0.5             xml2_1.3.2                 
## [243] httr_1.4.2                  assertthat_0.2.1           
## [245] rmarkdown_2.18              boot_1.3-25                
## [247] globals_0.14.0              R6_2.4.1                   
## [249] Rhdf5lib_1.8.0              nnet_7.3-14                
## [251] RcppHNSW_0.2.0              progress_1.2.2             
## [253] genefilter_1.68.0           statmod_1.4.34             
## [255] gtools_3.8.2                shape_1.4.6                
## [257] HDF5Array_1.14.4            BiocSingular_1.2.2         
## [259] rhdf5_2.30.1                splines_3.6.3              
## [261] AUCell_1.8.0                carData_3.0-4              
## [263] colorspace_1.4-1            generics_0.1.0             
## [265] stats4_3.6.3                base64enc_0.1-3            
## [267] dynfeature_1.0.0            smoother_1.1               
## [269] gridtext_0.1.1              pillar_1.6.3               
## [271] tweenr_1.0.1                sp_1.4-1                   
## [273] ggplot.multistats_1.0.0     rvcheck_0.1.8              
## [275] GenomeInfoDbData_1.2.2      plyr_1.8.6                 
## [277] gtable_0.3.0                zip_2.2.0                  
## [279] knitr_1.41                  latticeExtra_0.6-29        
## [281] biomaRt_2.42.1              IRanges_2.20.2             
## [283] fastmap_1.1.0               ADGofTest_0.3              
## [285] copula_1.0-0                doParallel_1.0.15          
## [287] AnnotationDbi_1.48.0        vcd_1.4-8                  
## [289] babelwhale_1.0.1            openssl_1.4.1              
## [291] scales_1.1.1                backports_1.2.1            
## [293] S4Vectors_0.24.4            ipred_0.9-12               
## [295] enrichplot_1.6.1            hms_1.1.1                  
## [297] ggforce_0.3.1               Rtsne_0.15                 
## [299] shiny_1.7.1                 numDeriv_2016.8-1.1        
## [301] polyclip_1.10-0             lazyeval_0.2.2             
## [303] Formula_1.2-3               tsne_0.1-3                 
## [305] crayon_1.3.4                MASS_7.3-54                
## [307] pROC_1.16.2                 viridis_0.5.1              
## [309] dynparam_1.0.0              rpart_4.1-15               
## [311] zinbwave_1.8.0              compiler_3.6.3             
## [313] ggtext_0.1.0
LS0tCnRpdGxlOiAiSFMgcHJvamVjdCIKc3VidGl0bGU6ICJDb21iaW5lZCBkYXRhc2V0IChvdXIgKyBXdSArIFRha2FoYXNoaSkiCmF1dGhvcjogIkF1ZHJleSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJVktJW0tJWQnKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQotLS0KCjxzdHlsZT4KYm9keSB7CnRleHQtYWxpZ246IGp1c3RpZnl9Cjwvc3R5bGU+Cgo8IS0tIEF1dG9tYXRpY2FsbHkgY29tcHV0ZXMgYW5kIHByaW50cyBpbiB0aGUgb3V0cHV0IHRoZSBydW5uaW5nIHRpbWUgZm9yIGFueSBjb2RlIGNodW5rIC0tPgpgYGB7ciwgZWNobz1GQUxTRX0KIyBodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9ybWFya2Rvd24vaXNzdWVzLzE0NTMKaG9va3MgPSBrbml0cjo6a25pdF9ob29rcyRnZXQoKQpob29rX2ZvbGRhYmxlID0gZnVuY3Rpb24odHlwZSkgewogIGZvcmNlKHR5cGUpCiAgZnVuY3Rpb24oeCwgb3B0aW9ucykgewogICAgcmVzID0gaG9va3NbW3R5cGVdXSh4LCBvcHRpb25zKQogICAgCiAgICBpZiAoaXNGQUxTRShvcHRpb25zW1twYXN0ZTAoImZvbGRfIiwgdHlwZSldXSkpIHJldHVybihyZXMpCiAgICAKICAgIHBhc3RlMCgKICAgICAgIjxkZXRhaWxzPjxzdW1tYXJ5PiIsICJzaG93IiwgIjwvc3VtbWFyeT5cblxuIiwKICAgICAgcmVzLAogICAgICAiXG5cbjwvZGV0YWlscz4iCiAgICApCiAgfQp9CmtuaXRyOjprbml0X2hvb2tzJHNldCgKICBvdXRwdXQgPSBob29rX2ZvbGRhYmxlKCJvdXRwdXQiKSwKICBwbG90ID0gaG9va19mb2xkYWJsZSgicGxvdCIpLAogIHRpbWVfaXQgPSBsb2NhbCh7CiAgICBub3cgPSBOVUxMCiAgICBmdW5jdGlvbihiZWZvcmUsIG9wdGlvbnMpIHsKICAgICAgaWYgKG9wdGlvbnMkdGltZV9pdCkgewogICAgICAgIGlmIChiZWZvcmUpIHsKICAgICAgICAgIG5vdyA8PC0gU3lzLnRpbWUoKQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICByZXMgPSBkaWZmdGltZShTeXMudGltZSgpLCBub3csIHVuaXRzID0gInNlY3MiKQogICAgICAgICAgcGFzdGUoIihUaW1lIHRvIHJ1biA6Iiwgcm91bmQocmVzLCBkaWdpdHMgPSAyKSwgInMpIikKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9KQopCmBgYAoKPCEtLSBTZXQgZGVmYXVsdCBwYXJhbWV0ZXJzIGZvciBhbGwgY2h1bmtzIC0tPgpgYGB7ciwgc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kc2V0LnNlZWQoMTMzN0wpCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgIyBkaXNwbGF5IGNvZGUKICAgICAgICAgICAgICAgICAgICAgICMgZGlzcGxheSBjaHVuayBvdXRwdXQKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZvbGRfb3V0cHV0ID0gRkFMU0UsICMgdXNlZnVsbCBmb3Igc2Vzc2lvbkluZm8oKQogICAgICAgICAgICAgICAgICAgICAgZm9sZF9wbG90ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICMgZmlndXJlIHNldHRpbmdzCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAnY2VudGVyJywKICAgICAgICAgICAgICAgICAgICAgIGZpZy53aWR0aCA9IDIwLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDE1LAogICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAjIHNvbWV0aGluZyBhYm91dCBzZWVkLCBjaHVuayBhbmQgUm1hcmtkb3duIGNvbXBpbGF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAjIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzM5NDE3MDAzL2xvbmctdmVjdG9ycy1ub3Qtc3VwcG9ydGVkLXlldC1lcnJvci1pbi1ybWQtYnV0LW5vdC1pbi1yLXNjcmlwdAogICAgICAgICAgICAgICAgICAgICAgIyBjYWNoZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5sYXp5ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAjIGFkZCBydW50aW1lIGFmdGVyIGNodW5rCiAgICAgICAgICAgICAgICAgICAgICB0aW1lX2l0ID0gRkFMU0UpCmBgYAoKClRoaXMgZmlsZSBpcyB1c2VkIHRvIGNvbWJpbmUgdGhyZWUgZGF0YXNldHM6CgoqIG91ciBkYXRhc2V0IHdpdGggNSBIUyBwYXRpZW50cyBhbmQgMiBoZWFsdGh5IGRvbm9ycwoqIFd1IGRhdGFzZXQgd2l0aCA2IHNhbXBsZXMgZnJvbSA0IGhlYWx0aHkgZG9ub3JzCiogVGFrYWhhc2hpIGRhdGFzZXQgd2l0aCA1IHNhbXBsZXMKCldlIGxvYWQgZWFjaCBpbmRpdmlkdWFsIHNhbXBsZSwgcmVtb3ZlIG1lbGFub2N5dGVzLCBhbmQgbWVyZ2UgdGhlIHJlbWFpbmluZyBjZWxscy4KCgpgYGB7ciBsaWJyYXJ5fQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQoKLmxpYlBhdGhzKCkKYGBgCgoKIyBQcmVwYXJhdGlvbgoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBzZXQgdGhlIGdsb2JhbCBzZXR0aW5ncyBvZiB0aGUgYW5hbHlzaXMuIFdlIHdpbGwgc3RvcmUgZGF0YSB0aGVyZSA6CgpgYGB7ciBvdXRfZGlyfQpzYXZlX25hbWUgPSAiZGF0YTMiCm91dF9kaXIgPSAiLiIKbl90aHJlYWRzID0gNSAjIGZvciB0U05FCmBgYAoKCldlIGNvbWJpbmUgdGhlIHRocmVlIHNhbXBsZSBpbmZvcm1hdGlvbiA6CgpgYGB7ciBjdXN0b21fcGFsZXR0ZV9zYW1wbGUsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA3fQpzYW1wbGVfaW5mb18xID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8xX21ldGFkYXRhL2hzX2hkX3NhbXBsZV9pbmZvLnJkcyIpKQpzYW1wbGVfaW5mb18yID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi81X3d1LzFfbWV0YWRhdGEvd3Vfc2FtcGxlX2luZm8ucmRzIikpCnNhbXBsZV9pbmZvXzMgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiLy4uLzZfdGFrYWhhc2hpLzFfbWV0YWRhdGEvdGFrYWhhc2hpX3NhbXBsZV9pbmZvLnJkcyIpKQoKY29sdW1uX3RvX2tlZXAgPSBjKCJwcm9qZWN0X25hbWUiLCAic2FtcGxlX3R5cGUiLCAic2FtcGxlX2lkZW50aWZpZXIiLAogICAgICAgICAgICAgICAgICAgInBsYXRmb3JtIiwgImdlbmRlciIsICJsb2NhdGlvbiIsICJsYWJvcmF0b3J5IiwgImNvbG9yIikKCnNhbXBsZV9pbmZvID0gcmJpbmQuZGF0YS5mcmFtZShzYW1wbGVfaW5mb18xWywgY29sdW1uX3RvX2tlZXBdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX2luZm9fMlssIGNvbHVtbl90b19rZWVwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9pbmZvXzNbLCBjb2x1bW5fdG9fa2VlcF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpncmFwaGljczo6cGllKHJlcCgxLCBucm93KHNhbXBsZV9pbmZvKSksCiAgICAgICAgICAgICAgY29sID0gc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgbGFiZWxzID0gc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lKQpgYGAKCkhlcmUgYXJlIGN1c3RvbSBjb2xvcnMgZm9yIGVhY2ggY2VsbCB0eXBlIDoKCmBgYHtyIGNvbG9yX21hcmtlcnMsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMS4yLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KY29sb3JfbWFya2VycyA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vMV9tZXRhZGF0YS9oc19oZF9jb2xvcl9tYXJrZXJzLnJkcyIpKQoKZGF0YS5mcmFtZShjZWxsX3R5cGUgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICBjb2xvciA9IHVubGlzdChjb2xvcl9tYXJrZXJzKSkgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KC4sIGFlcyh4ID0gY2VsbF90eXBlLCB5ID0gMCwgZmlsbCA9IGNlbGxfdHlwZSkpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KHBjaCA9IDIxLCBzaXplID0gNSkgKwogIGdncGxvdDI6OnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHVubGlzdChjb2xvcl9tYXJrZXJzKSwgYnJlYWtzID0gbmFtZXMoY29sb3JfbWFya2VycykpICsKICBnZ3Bsb3QyOjp0aGVtZV9jbGFzc2ljKCkgKwogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSkpCmBgYAoKV2UgbG9hZCB0aGUgbWFya2VycyBhbmQgc3BlY2lmaWMgY29sb3JzIGZvciBlYWNoIGNlbGwgdHlwZSA6CgpgYGB7ciBjZWxsX21hcmtlcnN9CmNlbGxfbWFya2VycyA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vMV9tZXRhZGF0YS9oc19oZF9jZWxsX21hcmtlcnMucmRzIikpCmxlbmd0aHMoY2VsbF9tYXJrZXJzKQpgYGAKCldlIGxvYWQgbWFya2VycyB0byBkaXNwbGF5IG9uIHRoZSBkb3RwbG90IDoKCmBgYHtyIGRvdHBsb3RfbWFya2Vyc30KZG90cGxvdF9tYXJrZXJzID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi8xX21ldGFkYXRhL2hzX2hkX2RvdHBsb3RfbWFya2Vycy5yZHMiKSkKZG90cGxvdF9tYXJrZXJzCmBgYAoKIyBNYWtlIGByIHNhdmVfbmFtZWAgZGF0YXNldAoKIyMgSW5kaXZpZHVhbCBkYXRhc2V0cwoKRm9yIGVhY2ggc2FtcGxlLCB3ZSA6CgoqIGxvYWQgaW5kaXZpZHVhbCBkYXRhc2V0CiogbG9vayBhdCBjZWxsIGFubm90YXRpb24KCldlIGxvYWQgaW5kaXZpZHVhbCBkYXRhc2V0cyA6CgpgYGB7ciBzb2JqX2xpc3R9CnNvYmpfbGlzdCA9IGxpc3QoKQoKIyBPdXIgZGF0YQpwcm9qZWN0X25hbWVzX29pID0gc2FtcGxlX2luZm9fMSRwcm9qZWN0X25hbWUKc29ial9saXN0W1siaGVyZSJdXSA9IGxhcHBseShwcm9qZWN0X25hbWVzX29pLCBGVU4gPSBmdW5jdGlvbihvbmVfcHJvamVjdF9uYW1lKSB7CiAgc3Vic29iaiA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vMl9pbmRpdmlkdWFsL2RhdGFzZXRzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG9uZV9wcm9qZWN0X25hbWUsICJfc29ial9maWx0ZXJlZC5yZHMiKSkKICByZXR1cm4oc3Vic29iaikKfSkKbmFtZXMoc29ial9saXN0W1siaGVyZSJdXSkgPSBwcm9qZWN0X25hbWVzX29pCgojIFd1IGRhdGEKcHJvamVjdF9uYW1lc19vaSA9IHNhbXBsZV9pbmZvXzIkcHJvamVjdF9uYW1lCnNvYmpfbGlzdFtbInd1Il1dID0gbGFwcGx5KHByb2plY3RfbmFtZXNfb2ksIEZVTiA9IGZ1bmN0aW9uKG9uZV9wcm9qZWN0X25hbWUpIHsKICBzdWJzb2JqID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi8uLi81X3d1LzJfaW5kaXZpZHVhbC9kYXRhc2V0cy8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICBvbmVfcHJvamVjdF9uYW1lLCAiX3NvYmpfZmlsdGVyZWQucmRzIikpCiAgcmV0dXJuKHN1YnNvYmopCn0pCm5hbWVzKHNvYmpfbGlzdFtbInd1Il1dKSA9IHByb2plY3RfbmFtZXNfb2kKCiMgVGFrYWhhc2hpIGRhdGEKcHJvamVjdF9uYW1lc19vaSA9IHNhbXBsZV9pbmZvXzMkcHJvamVjdF9uYW1lCnNvYmpfbGlzdFtbInRha2FoYXNoaSJdXSA9IGxhcHBseShwcm9qZWN0X25hbWVzX29pLCBGVU4gPSBmdW5jdGlvbihvbmVfcHJvamVjdF9uYW1lKSB7CiAgc3Vic29iaiA9IHJlYWRSRFMocGFzdGUwKG91dF9kaXIsICIvLi4vNl90YWthaGFzaGkvMl9pbmRpdmlkdWFsL2RhdGFzZXRzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG9uZV9wcm9qZWN0X25hbWUsICJfc29ial9maWx0ZXJlZC5yZHMiKSkKICByZXR1cm4oc3Vic29iaikKfSkKbmFtZXMoc29ial9saXN0W1sidGFrYWhhc2hpIl1dKSA9IHByb2plY3RfbmFtZXNfb2kKCiMgVW5saXN0CnNvYmpfbGlzdCA9IHVubGlzdChzb2JqX2xpc3QsIHJlY3Vyc2l2ZSA9IEZBTFNFKQoKbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZGltKSAlPiUKICBkby5jYWxsKHJiaW5kLCAuKSAlPiUKICByYmluZCguLCBjb2xTdW1zKC4pKQpgYGAKCgpXZSByZXByZXNlbnQgY2VsbHMgaW4gdGhlIHRTTkUgOgoKYGBge3IgbmFtZTJEfQpuYW1lMkQgPSAiUk5BX3BjYV8yMF90c25lIgpgYGAKCgpXZSBsb29rIGF0IGNlbGwgdHlwZSBhbm5vdGF0aW9uIGZvciBlYWNoIGRhdGFzZXQgOgoKYGBge3IgY2VsbF90eXBlX3Byb2osIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gMjB9CnBsb3RfbGlzdCA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9zb2JqKSB7CiAgbXl0aXRsZSA9IGFzLmNoYXJhY3Rlcih1bmlxdWUob25lX3NvYmokcHJvamVjdF9uYW1lKSkKICBteXN1YnRpdGxlID0gbmNvbChvbmVfc29iaikKICAKICBpZiAoIShuYW1lMkQgJWluJSBuYW1lcyhvbmVfc29iakByZWR1Y3Rpb25zKSkpIHsKICAgIG5hbWUyRCA9IG5hbWVzKG9uZV9zb2JqQHJlZHVjdGlvbnMpWzJdCiAgfQogIAogIHAgPSBTZXVyYXQ6OkRpbVBsb3Qob25lX3NvYmosIGdyb3VwLmJ5ID0gImNlbGxfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQpICsKICAgIGdncGxvdDI6OnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcl9tYXJrZXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ2VsbCBUeXBlIikgKwogICAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9IG15dGl0bGUsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKG15c3VidGl0bGUsICIgY2VsbHMiKSkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICAgIFNldXJhdDo6Tm9BeGVzKCkKICAKICByZXR1cm4ocCkKfSkKCnBsb3RfbGlzdFtbbGVuZ3RoKHBsb3RfbGlzdCkgKyAxXV0gPSBwYXRjaHdvcms6Omd1aWRlX2FyZWEoKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpICsKICBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJgogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKCmFuZCBjbHVzdGVyaW5nIDoKCgpgYGB7ciBjbHVzdGVyaW5nX3Byb2osIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gMjB9CnBsb3RfbGlzdCA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9zb2JqKSB7CiAgbXl0aXRsZSA9IGFzLmNoYXJhY3Rlcih1bmlxdWUob25lX3NvYmokcHJvamVjdF9uYW1lKSkKICBteXN1YnRpdGxlID0gbmNvbChvbmVfc29iaikKICAKICBpZiAoIShuYW1lMkQgJWluJSBuYW1lcyhvbmVfc29iakByZWR1Y3Rpb25zKSkpIHsKICAgIG5hbWUyRCA9IG5hbWVzKG9uZV9zb2JqQHJlZHVjdGlvbnMpWzJdCiAgfQogIAogIHAgPSBTZXVyYXQ6OkRpbVBsb3Qob25lX3NvYmosIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsIGxhYmVsID0gVFJVRSkgKwogICAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9IG15dGl0bGUsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKG15c3VidGl0bGUsICIgY2VsbHMiKSkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICAgIFNldXJhdDo6Tm9BeGVzKCkgKyBTZXVyYXQ6Ok5vTGVnZW5kKCkKICAKICByZXR1cm4ocCkKfSkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSA0KQpgYGAKCiMjIE1lbGFub2N5dGVzIHJlbW92YWwKCkZvciBlYWNoIGluZGl2aWR1YWwgZGF0YXNldCwgd2UgcmVtb3ZlIG1lbGFub2N5dGVzLiBGaXJzdCwgd2Ugc21vb3RoIGNlbGwgdHlwZSBhbm5vdGF0aW9uIGF0IGEgY2x1c3RlciBsZXZlbCA6CgpgYGB7ciBzbW9vdGhfYW5ub3RhdGlvbn0Kc29ial9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICBjbHVzdGVyX3R5cGUgPSB0YWJsZShvbmVfc29iaiRjZWxsX3R5cGUsIG9uZV9zb2JqJHNldXJhdF9jbHVzdGVycykgJT4lCiAgICBwcm9wLnRhYmxlKC4sIG1hcmdpbiA9IDIpICU+JQogICAgYXBwbHkoLiwgMiwgd2hpY2gubWF4KQogIGNsdXN0ZXJfdHlwZSA9IHNldE5hbWVzKG5tID0gbmFtZXMoY2x1c3Rlcl90eXBlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMob25lX3NvYmokY2VsbF90eXBlKVtjbHVzdGVyX3R5cGVdKQogIAogIG9uZV9zb2JqJGNsdXN0ZXJfdHlwZSA9IGNsdXN0ZXJfdHlwZVtvbmVfc29iaiRzZXVyYXRfY2x1c3RlcnNdCiAgCiAgIyMgT3V0cHV0CiAgcmV0dXJuKG9uZV9zb2JqKQp9KQpgYGAKClRvIGxvY2F0ZSBtZWxhbm9jeXRlcywgd2UgbG9vayBhdCB0aGVpciBzY29yZSwgY2VsbCB0eXBlIGFubm90YXRpb24sIGFuZCBjbHVzdGVyaW5nLgoKYGBge3IgcGxvdF9jZWxsX3R5cGUsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNTB9CnBsb3RfbGlzdCA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9zb2JqKSB7CiAgcHJvamVjdF9uYW1lID0gYXMuY2hhcmFjdGVyKHVuaXF1ZShvbmVfc29iaiRwcm9qZWN0X25hbWUpKQogIHBsb3Rfc3VibGlzdCA9IGxpc3QoKQogIAogIGlmICghKG5hbWUyRCAlaW4lIG5hbWVzKG9uZV9zb2JqQHJlZHVjdGlvbnMpKSkgewogICAgbmFtZTJEID0gbmFtZXMob25lX3NvYmpAcmVkdWN0aW9ucylbMl0KICB9CiAgCiAgIyBTY29yZQogIHBsb3Rfc3VibGlzdFtbMV1dID0gU2V1cmF0OjpGZWF0dXJlUGxvdChvbmVfc29iaiwgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcyA9ICJzY29yZV9tZWxhbm9jeXRlcyIpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSBwcm9qZWN0X25hbWUsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gIk1lbGFub2N5dGVzIHNjb3JlIikgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYXF1YXJpdXM6Ojpjb2xvcl9nZW5lKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCiAgCiAgIyBDZWxsIHR5cGUKICBwbG90X3N1Ymxpc3RbWzJdXSA9IFNldXJhdDo6RGltUGxvdChvbmVfc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuYnkgPSAiY2VsbF90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9ICJtZWxhbm9jeXRlcyIpICsKICAgIGdncGxvdDI6OnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJwdXJwbGUiLCByZXAoImdyYXk5MiIsIGxlbmd0aChjb2xvcl9tYXJrZXJzKSAtIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKCJtZWxhbm9jeXRlcyIsIHNldGRpZmYobmFtZXMoY29sb3JfbWFya2VycyksICJtZWxhbm9jeXRlcyIpKSkgKwogICAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDZWxsIHR5cGUgYW5ub3RhdGlvbiIsCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKHN1bShvbmVfc29iaiRjZWxsX3R5cGUgPT0gIm1lbGFub2N5dGVzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIgbWVsYW5vY3l0ZXMiKSkgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAKICAjIENsdXN0ZXJzCiAgcGxvdF9zdWJsaXN0W1szXV0gPSBTZXVyYXQ6OkRpbVBsb3Qob25lX3NvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFKSArCiAgICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkNsdXN0ZXJzIikgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAKICAjIENsdXN0ZXIgdHlwZQogIHBsb3Rfc3VibGlzdFtbNF1dID0gU2V1cmF0OjpEaW1QbG90KG9uZV9zb2JqLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJjbHVzdGVyX3R5cGUiKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicHVycGxlIiwgcmVwKCJncmF5OTIiLCBsZW5ndGgoY29sb3JfbWFya2VycykgLSAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gYygibWVsYW5vY3l0ZXMiLCBzZXRkaWZmKG5hbWVzKGNvbG9yX21hcmtlcnMpLCAibWVsYW5vY3l0ZXMiKSkpICsKICAgIGdncGxvdDI6OmxhYnModGl0bGUgPSAiQ2x1c3RlciBhbm5vdGF0aW9uIiwKICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSBwYXN0ZTAoc3VtKG9uZV9zb2JqJGNsdXN0ZXJfdHlwZSA9PSAibWVsYW5vY3l0ZXMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBtZWxhbm9jeXRlcyIpKSArCiAgICBTZXVyYXQ6Ok5vQXhlcygpICsgU2V1cmF0OjpOb0xlZ2VuZCgpICsKICAgIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQogIAogIHJldHVybihwbG90X3N1Ymxpc3QpCn0pICU+JSB1bmxpc3QoLiwgcmVjdXJzaXZlID0gRkFMU0UpCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBuY29sID0gNCkKYGBgCgpXZSByZW1vdmUgbWVsYW5vY3l0ZXMgYmFzZWQgb24gY2x1c3RlciBhbm5vdGF0aW9uIGZvciAxMFggZGF0YXNldHMgYW5kIGJhc2VkIG9uIHRoZSBjZWxsIHR5cGUgYW5ub3RhdGlvbiBmb3IgRHJvcC1TZXEgZGF0YXNldHMgOgoKYGBge3IgcmVtb3ZlX21lbGFub2N5dGVzfQpzb2JqX2xpc3QgPSBsYXBwbHkoc29ial9saXN0LCBGVU4gPSBmdW5jdGlvbihvbmVfc29iaikgewogIGlmIChvbmVfc29iakBwcm9qZWN0Lm5hbWUgJWluJSBjKCJHU00zNzE3MDM0IiwgIkdTTTM3MTcwMzUiLCAiR1NNMzcxNzAzNiIpKSB7CiAgICBvbmVfc29iaiRpc19vZl9pbnRlcmVzdCA9IChvbmVfc29iaiRjZWxsX3R5cGUgIT0gIm1lbGFub2N5dGVzIikKICB9IGVsc2UgewogICAgb25lX3NvYmokaXNfb2ZfaW50ZXJlc3QgPSAob25lX3NvYmokY2x1c3Rlcl90eXBlICE9ICJtZWxhbm9jeXRlcyIpCiAgfQogIAogIGlmIChzdW0ob25lX3NvYmokaXNfb2ZfaW50ZXJlc3QpID4gMCkgewogICAgb25lX3NvYmogPSBzdWJzZXQob25lX3NvYmosIGlzX29mX2ludGVyZXN0ID09IFRSVUUpCiAgfSBlbHNlIHsKICAgIG9uZV9zb2JqID0gTkEKICB9CiAgCiAgb25lX3NvYmokaXNfb2ZfaW50ZXJlc3QgPSBOVUxMCiAgcmV0dXJuKG9uZV9zb2JqKQp9KQoKbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZGltKSAlPiUKICBkby5jYWxsKHJiaW5kLCAuKSAlPiUKICByYmluZCguLCBjb2xTdW1zKC4pKQpgYGAKCiMjIFJlLWFubm90YXRpb24KCldlIHJlbW92ZSBtZWxhbm9jeXRlcyBmcm9tIGFubm90YXRpb24gOgoKYGBge3IgcmVtb3ZlX2Zyb21fYW5ub3R9CmNlbGxfbWFya2VycyA9IGNlbGxfbWFya2Vyc1tuYW1lcyhjZWxsX21hcmtlcnMpICE9ICJtZWxhbm9jeXRlcyJdCmNvbG9yX21hcmtlcnMgPSBjb2xvcl9tYXJrZXJzW25hbWVzKGNvbG9yX21hcmtlcnMpICE9ICJtZWxhbm9jeXRlcyJdCmRvdHBsb3RfbWFya2VycyA9IGRvdHBsb3RfbWFya2Vyc1tuYW1lcyhkb3RwbG90X21hcmtlcnMpICE9ICJtZWxhbm9jeXRlcyJdCmBgYAoKV2UgcmUtYW5ub3RhdGUgY2VsbHMgZm9yIGNlbGwgdHlwZSwgc2luY2UgbWVsYW5vY3l0ZXMgaGF2ZSBiZWVuIHJlbW92ZWQgOgoKYGBge3IgcmVfYW5ub3R9CnNvYmpfbGlzdCA9IGxhcHBseShzb2JqX2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9zb2JqKSB7CiAgIyBSZW1vdmUgb2xkIGFubm90YXRpb24KICBvbmVfc29iakBtZXRhLmRhdGFbLCBncmVwKGNvbG5hbWVzKG9uZV9zb2JqQG1ldGEuZGF0YSksIHBhdHRlcm4gPSAic2NvcmUiLCB2YWx1ZSA9IFRSVUUpXSA9IE5VTEwKICAKICAjIFJlLWFubm90CiAgb25lX3NvYmogPSBhcXVhcml1czo6Y2VsbF9hbm5vdF9jdXN0b20ob25lX3NvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3bmFtZSA9ICJjZWxsX3R5cGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmtlcnMgPSBjZWxsX21hcmtlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlX25lZ2F0aXZlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZGRfc2NvcmUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSkKICAKICAjIFNldCBmYWN0b3IgbGV2ZWxzCiAgb25lX3NvYmokY2VsbF90eXBlID0gZmFjdG9yKG9uZV9zb2JqJGNlbGxfdHlwZSwgbGV2ZWxzID0gbmFtZXMoY2VsbF9tYXJrZXJzKSkKICAKICByZXR1cm4ob25lX3NvYmopCn0pCmBgYAoKIyMgR2VuZSBhbm5vdGF0aW9uCgpPdXIgZGF0YXNldCBhbmQgV3UgZGF0YXNldCB3ZXJlIHByb2Nlc3NlZCB1c2luZyB0aGUgc2FtZSBhbm5vdGF0aW9uLiBJbiBUYWthaGFzaGkgZGF0YXNldCwgYWxsIGdlbmVzIGFyZSBub3Qgc2hhcmVkIGFjcm9zcyBkYXRhc2V0czoKCk5vdGU6IFdpdGggdGhlIGBnZ3Zlbm5gIHBhY2thZ2UsIHRoaXMgaXMgbm90IHBvc3NpYmxlIHRvIG1ha2UgYSBWZW5uIGRpYWdyYW0gd2l0aCA1IHNldHMuCgpgYGB7ciBnZ3Zlbm5fZHNfMTB4LCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KZ2d2ZW5uOjpnZ3Zlbm4oZGF0YSA9IGxpc3QoCiAgaGVyZS4yMDIxXzMxID0gc29ial9saXN0W1siaGVyZS4yMDIxXzMxIl1dQGFzc2F5c1tbIlJOQSJdXUBtZXRhLmZlYXR1cmVzJEVuc2VtYmxfSUQsCiAgd3UuRjE4ID0gc29ial9saXN0W1sid3UuRjE4Il1dQGFzc2F5c1tbIlJOQSJdXUBtZXRhLmZlYXR1cmVzJEVuc2VtYmxfSUQsCiAgdGFrYWhhc2hpLkdTTTM3MTcwMzQgPSBzb2JqX2xpc3RbWyJ0YWthaGFzaGkuR1NNMzcxNzAzNCJdXUBhc3NheXNbWyJSTkEiXV1AbWV0YS5mZWF0dXJlcyRFbnNlbWJsX0lELAogIHRha2FoYXNoaS5HU00zNzE3MDM4ID0gc29ial9saXN0W1sidGFrYWhhc2hpLkdTTTM3MTcwMzgiXV1AYXNzYXlzW1siUk5BIl1dQG1ldGEuZmVhdHVyZXMkRW5zZW1ibF9JRCksCiAgc3Ryb2tlX3NpemUgPSAwLjUsIHNldF9uYW1lX3NpemUgPSA0KSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJHZW5lIEVuc2VtYmwgSURzIGJldHdlZW4gdGhlIDQgZGF0YXNldHMiKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIikpCmBgYAoKV2Uga2VlcCBjb21tb24gZ2VuZXMgYmV0d2VlbiBhbGwgZGF0YXNldHMgKyBjb21tb24gZ2VuZXMgYmV0d2VlbiB0aGUgMTBYIGRhdGFzZXRzLCBiYXNlZCBvbiB0aGUgRW5zZW1ibElECgpgYGB7ciBjb21tb25fZ2VuZXMsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA4fQojIEFsbCBFbnNlbWJsIElEcwpjb21tb25fZ2VuZXMgPSBsYXBwbHkoc29ial9saXN0LCBGVU4gPSBmdW5jdGlvbihvbmVfc29iaikgewogIGVuc2VtYmxfaWQgPSBvbmVfc29iakBhc3NheXNbWyJSTkEiXV1AbWV0YS5mZWF0dXJlcyRFbnNlbWJsX0lECiAgCiAgcmV0dXJuKGVuc2VtYmxfaWQpCn0pCm5hbWVzKGNvbW1vbl9nZW5lcykgPSBuYW1lcyhzb2JqX2xpc3QpCgojIENvbW1vbiBiZXR3ZWVuIDEwWCBkYXRhc2V0cwpjb21tb25fZ2VuZXNfMTB4ID0gY29tbW9uX2dlbmVzWyEobmFtZXMoY29tbW9uX2dlbmVzKSAlaW4lIGMoInRha2FoYXNoaS5HU00zNzE3MDM0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0YWthaGFzaGkuR1NNMzcxNzAzNSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidGFrYWhhc2hpLkdTTTM3MTcwMzYiKSldICU+JQogIFJlZHVjZShpbnRlcnNlY3QsIC4pCgojIENvbW1vbiBiZXR3ZWVuIGFsbApjb21tb25fZ2VuZXMgPSBSZWR1Y2UoaW50ZXJzZWN0LCBjb21tb25fZ2VuZXMpCgojIFZlbm4gZGlhZ3JhbQpnZ3Zlbm46OmdndmVubihkYXRhID0gbGlzdCgKICBjb21tb25fYWxsID0gY29tbW9uX2dlbmVzLAogIGNvbW1vbl8xMFggPSBjb21tb25fZ2VuZXNfMTB4KSwKICBzdHJva2Vfc2l6ZSA9IDAuNSwgc2V0X25hbWVfc2l6ZSA9IDQpICsKICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gIkdlbmUgRW5zZW1ibCBJRHMiKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIikpCmBgYAoKV2Uga2VlcCB0aGUgdW5pb24gb2YgYWxsIHRoZXNlIGdlbmVzIDoKCmBgYHtyIGNvbW1vbl9nZW5lc191bmlvbn0KY29tbW9uX2dlbmVzID0gdW5pb24oY29tbW9uX2dlbmVzLCBjb21tb25fZ2VuZXNfMTB4KQpybShjb21tb25fZ2VuZXNfMTB4KQoKbGVuZ3RoKGNvbW1vbl9nZW5lcykKYGBgCgpUbyB3aGljaCBnZW5lIG5hbWVzIHRoZXkgY29ycmVzcG9uZCwgaW4gb25lIG9mIG91ciBkYXRhc2V0ID8KCmBgYHtyIGdlbmVfY29ycmVzcH0KZ2VuZV9jb3JyZXNwID0gc29ial9saXN0W1siaGVyZS4yMDIxXzMxIl1dQGFzc2F5cyRSTkFAbWV0YS5mZWF0dXJlcyAlPiUKICBkcGx5cjo6ZmlsdGVyKEVuc2VtYmxfSUQgJWluJSBjb21tb25fZ2VuZXMpICU+JQogIGRwbHlyOjpzZWxlY3QoRW5zZW1ibF9JRCwgZ2VuZV9uYW1lKQoKZGltKGdlbmVfY29ycmVzcCkKaGVhZChnZW5lX2NvcnJlc3ApCmBgYAoKV2Ugc3Vic2V0IFNldXJhdCBvYmplY3QgZm9yIHRoZSBFbnNlbWJsIElEcyBvZiBpbnRlcmVzdC4KCmBgYHtyIHN1YnNldF9nZW5lc30Kc29ial9saXN0ID0gbGFwcGx5KHNvYmpfbGlzdCwgRlVOID0gZnVuY3Rpb24ob25lX3NvYmopIHsKICAjIEV4dHJhY3QgbWV0YWRhdGEKICBvbmVfbWV0YWRhdGEgPSBvbmVfc29iakBtZXRhLmRhdGEKICAKICAjIEV4dHJhY3QgYW5kIHN1YnNldCBnZW5lIGFubm90YXRpb24KICBvbmVfYW5ub3RhdGlvbiA9IG9uZV9zb2JqQGFzc2F5c1tbIlJOQSJdXUBtZXRhLmZlYXR1cmVzICU+JQogICAgZHBseXI6OmZpbHRlcihFbnNlbWJsX0lEICVpbiUgZ2VuZV9jb3JyZXNwJEVuc2VtYmxfSUQpCiAgCiAgIyBTdWJzZXQgZ2VuZSBjb3JyZXNwIGZvciByZW9yZGVyaW5nCiAgb25lX2dlbmVfY29ycmVzcCA9IGdlbmVfY29ycmVzcCAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoRW5zZW1ibF9JRCAlaW4lIG9uZV9hbm5vdGF0aW9uJEVuc2VtYmxfSUQpCiAgCiAgIyBFeHRyYWN0IGNvdW50IG1hdHJpeCBhbmQgc3Vic2V0IGdlbmVzCiAgb25lX2NvdW50X21hdHJpeCA9IG9uZV9zb2JqQGFzc2F5c1tbIlJOQSJdXUBjb3VudHMKICBvbmVfY291bnRfbWF0cml4ID0gb25lX2NvdW50X21hdHJpeFtyb3duYW1lcyhvbmVfYW5ub3RhdGlvbiksIF0KICAKICAjIFJlb3JkZXIgYWNjb3JkaW5nIHRvIHRoZSBnZW5lIGNvcnJlc3BvbmRlbmNlCiAgZ2VuZV9vcmRlciA9IG1hdGNoKG9uZV9nZW5lX2NvcnJlc3AkRW5zZW1ibF9JRCwKICAgICAgICAgICAgICAgICAgICAgb25lX2Fubm90YXRpb24kRW5zZW1ibF9JRCkKICAKICAjIFJlb3JkZXIgdGhlIGNvdW50IG1hdHJpeCBhbmQgYW5ub3RhdGlvbgogIG9uZV9hbm5vdGF0aW9uID0gb25lX2Fubm90YXRpb25bZ2VuZV9vcmRlciwgXQogIG9uZV9jb3VudF9tYXRyaXggPSBvbmVfY291bnRfbWF0cml4W2dlbmVfb3JkZXIsIF0KICByb3duYW1lcyhvbmVfY291bnRfbWF0cml4KSA9IHJvd25hbWVzKG9uZV9nZW5lX2NvcnJlc3ApCiAgCiAgIyBCdWlsZCBhZ2FpbiB0aGUgU2V1cmF0IG9iamVjdAogIG9uZV9zb2JqID0gU2V1cmF0OjpDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gb25lX2NvdW50X21hdHJpeCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGEuZGF0YSA9IG9uZV9tZXRhZGF0YSkKICBvbmVfc29iakBhc3NheXNbWyJSTkEiXV1AbWV0YS5mZWF0dXJlcyA9IG9uZV9nZW5lX2NvcnJlc3AKICAKICByZXR1cm4ob25lX3NvYmopCn0pCgpsYXBwbHkoc29ial9saXN0LCBGVU4gPSBkaW0pICU+JQogIGRvLmNhbGwocmJpbmQsIC4pICU+JQogIHJiaW5kKC4sIGNvbFN1bXMoLikpCmBgYAoKIyMgQ29tYmluZWQgZGF0YXNldAoKV2UgY29tYmluZSBhbGwgZGF0YXNldHMgOgoKYGBge3IgbWVyZ2VfZGF0YXNldHN9CnNvYmogPSBiYXNlOjptZXJnZShzb2JqX2xpc3RbWzFdXSwKICAgICAgICAgICAgICAgICAgIHkgPSBzb2JqX2xpc3RbYygyOmxlbmd0aChzb2JqX2xpc3QpKV0sCiAgICAgICAgICAgICAgICAgICBhZGQuY2VsbC5pZHMgPSBuYW1lcyhzb2JqX2xpc3QpKQpzb2JqCmBgYAoKV2UgYWRkIGFnYWluIHRoZSBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGdlbmUgbmFtZXMgYW5kIGdlbmUgSUQuIFdlIHRha2UgdGhlIGNvcnJlc3BvbmRlbmNlIGZyb20gb25lIGluZGl2aWR1YWwgMTBYIGRhdGFzZXQuCgpgYGB7ciBhZGRfbWV0YWZlYXR1cmVzfQpzb2JqQGFzc2F5cyRSTkFAbWV0YS5mZWF0dXJlcyA9IHNvYmpfbGlzdFtbMV1dQGFzc2F5cyRSTkFAbWV0YS5mZWF0dXJlc1ssIGMoIkVuc2VtYmxfSUQiLCAiZ2VuZV9uYW1lIildCgpoZWFkKHNvYmpAYXNzYXlzJFJOQUBtZXRhLmZlYXR1cmVzKQpgYGAKCldlIHJlbW92ZSB0aGUgbGlzdCBvZiBvYmplY3RzIDoKCmBgYHtyIGNsZWFuX3NvYmpfbGlzdH0Kcm0oc29ial9saXN0KQpgYGAKCldlIGtlZXAgYSBzdWJzZXQgb2YgbWV0YS5kYXRhIGFuZCByZXNldCBsZXZlbHMgOgoKYGBge3Igc29ial9zZXRfZmFjdG9yX2xldmVsc30Kc29iakBtZXRhLmRhdGEgPSBzb2JqQG1ldGEuZGF0YVssIGMoIm9yaWcuaWRlbnQiLCAibkNvdW50X1JOQSIsICJuRmVhdHVyZV9STkEiLCAibG9nX25Db3VudF9STkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicHJvamVjdF9uYW1lIiwgInNhbXBsZV9pZGVudGlmaWVyIiwgInNhbXBsZV90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhYm9yYXRvcnkiLCAibG9jYXRpb24iLCAiU2V1cmF0LlBoYXNlIiwgImN5Y2xvbmUuUGhhc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicGVyY2VudC5tdCIsICJwZXJjZW50LnJiIiwgImNlbGxfdHlwZSIpXQoKc29iaiRvcmlnLmlkZW50ID0gZmFjdG9yKHNvYmokb3JpZy5pZGVudCwgbGV2ZWxzID0gbGV2ZWxzKHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSkpCnNvYmokcHJvamVjdF9uYW1lID0gZmFjdG9yKHNvYmokcHJvamVjdF9uYW1lLCBsZXZlbHMgPSBsZXZlbHMoc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lKSkKc29iaiRzYW1wbGVfaWRlbnRpZmllciA9IGZhY3Rvcihzb2JqJHNhbXBsZV9pZGVudGlmaWVyLCBsZXZlbHMgPSBsZXZlbHMoc2FtcGxlX2luZm8kc2FtcGxlX2lkZW50aWZpZXIpKQpzb2JqJHNhbXBsZV90eXBlID0gZmFjdG9yKHNvYmokc2FtcGxlX3R5cGUsIGxldmVscyA9IHVuaXF1ZShzYW1wbGVfaW5mbyRzYW1wbGVfdHlwZSkpCnNvYmokY2VsbF90eXBlID0gZmFjdG9yKHNvYmokY2VsbF90eXBlLCBsZXZlbHMgPSBuYW1lcyhjb2xvcl9tYXJrZXJzKSkKCnN1bW1hcnkoc29iakBtZXRhLmRhdGEpCmBgYAoKIyBQcm9jZXNzaW5nCgpXZSByZW1vdmUgZ2VuZXMgdGhhdCBhcmUgZXhwcmVzc2VkIGluIGxlc3MgdGhhbiA1IGNlbGxzIDoKCmBgYHtyIGZpbHRlcl9nZW5lc30Kc29iaiA9IGFxdWFyaXVzOjpmaWx0ZXJfZmVhdHVyZXMoc29iaiwgbWluX2NlbGxzID0gNSkKc29iagpgYGAKCgojIyBNZXRhZGF0YQoKSG93IG1hbnkgY2VsbHMgYnkgc2FtcGxlID8KCmBgYHtyIHRhYmxlX29yaWdfaWRlbnR9CnRhYmxlKHNvYmokcHJvamVjdF9uYW1lKQpgYGAKCldlIHJlcHJlc2VudCB0aGlzIGluZm9ybWF0aW9uIGFzIGEgYmFycGxvdCA6CgpgYGB7ciBiYXJwbG90X2NvdW50LCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9CmFxdWFyaXVzOjpwbG90X2JhcnBsb3QoZGYgPSB0YWJsZShzb2JqJHByb2plY3RfbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvYmokY2VsbF90eXBlKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIGBjb2xuYW1lczwtYChjKCJTYW1wbGUiLCAiQ2VsbCBUeXBlIiwgIk51bWJlciIpKSwKICAgICAgICAgICAgICAgICAgICAgICB4ID0gIlNhbXBsZSIsIHkgPSAiTnVtYmVyIiwgZmlsbCA9ICJDZWxsIFR5cGUiLAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCgpKSArCiAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdW5saXN0KGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ2VsbCBUeXBlIikKYGBgCgpUaGlzIGlzIHRoZSBzYW1lIGJhcnBsb3Qgd2l0aCBhbm90aGVyIHBvc2l0aW9uIDoKCmBgYHtyIGJhcnBsb3Rfc3RhY2ssIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNX0KYXF1YXJpdXM6OnBsb3RfYmFycGxvdChkZiA9IHRhYmxlKHNvYmokcHJvamVjdF9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc29iaiRjZWxsX3R5cGUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZS50YWJsZSgpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgYGNvbG5hbWVzPC1gKGMoIlNhbXBsZSIsICJDZWxsIFR5cGUiLCAiTnVtYmVyIikpLAogICAgICAgICAgICAgICAgICAgICAgIHggPSAiU2FtcGxlIiwgeSA9ICJOdW1iZXIiLCBmaWxsID0gIkNlbGwgVHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjaygpKSArCiAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gdW5saXN0KGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbG9yX21hcmtlcnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ2VsbCBUeXBlIikKYGBgCgojIyBQcm9qZWN0aW9uCgpXZSBub3JtYWxpemUgdGhlIGNvdW50IG1hdHJpeCBmb3IgcmVtYWluaW5nIGNlbGxzIGFuZCBzZWxlY3QgaGlnaGx5IHZhcmlhYmxlIGZlYXR1cmVzIDoKCmBgYHtyIG5vcm1hbGl6YXRpb259CnNvYmogPSBTZXVyYXQ6Ok5vcm1hbGl6ZURhdGEoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiKQpzb2JqID0gU2V1cmF0OjpGaW5kVmFyaWFibGVGZWF0dXJlcyhzb2JqLCBuZmVhdHVyZXMgPSAyMDAwKQpzb2JqID0gU2V1cmF0OjpTY2FsZURhdGEoc29iaikKCnNvYmoKYGBgCgpXZSBwZXJmb3JtIGEgUENBIDoKCmBgYHtyIHBjYX0Kc29iaiA9IFNldXJhdDo6UnVuUENBKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJSTkEiLAogICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLm5hbWUgPSAiUk5BX3BjYSIsCiAgICAgICAgICAgICAgICAgICAgICBucGNzID0gMTAwLAogICAgICAgICAgICAgICAgICAgICAgc2VlZC51c2UgPSAxMzM3TCkKc29iagpgYGAKCldlIGNob29zZSB0aGUgbnVtYmVyIG9mIGRpbWVuc2lvbnMgc3VjaCB0aGF0IHRoZXkgc3VtbWFyaXplIDYwICUgb2YgdGhlIHZhcmlhYmlsaXR5IDoKCmBgYHtyIG5kaW1zfQpzdGRldiA9IHNvYmpAcmVkdWN0aW9uc1tbIlJOQV9wY2EiXV1Ac3RkZXYKc3RkZXZfcHJvcCA9IGN1bXN1bShzdGRldikvc3VtKHN0ZGV2KQpuZGltcyA9IHdoaWNoKHN0ZGV2X3Byb3AgPiAwLjYwKVsxXQpuZGltcwpgYGAKCldlIGNhbiB2aXN1YWxpemUgdGhpcyBvbiB0aGUgZWxib3cgcGxvdCA6CgpgYGB7ciBlbGJvd3Bsb3QsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNH0KZWxib3dfcCA9IFNldXJhdDo6RWxib3dQbG90KHNvYmosIG5kaW1zID0gMTAwLCByZWR1Y3Rpb24gPSAiUk5BX3BjYSIpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KHggPSBuZGltcywgeSA9IHN0ZGV2W25kaW1zXSwgY29sID0gInJlZCIpCnhfdGV4dCA9IGdncGxvdF9idWlsZChlbGJvd19wKSRsYXlvdXQkcGFuZWxfcGFyYW1zW1sxXV0keCRnZXRfbGFiZWxzKCkgJT4lIGFzLm51bWVyaWMoKQplbGJvd19wID0gZWxib3dfcCArCiAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNvcnQoYyh4X3RleHQsIG5kaW1zKSksIGxpbWl0cyA9IGMoMCwgMTAwKSkKeF9jb2xvciA9IGlmZWxzZShnZ3Bsb3RfYnVpbGQoZWxib3dfcCkkbGF5b3V0JHBhbmVsX3BhcmFtc1tbMV1dJHgkZ2V0X2xhYmVscygpICU+JQogICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYygpICU+JSByb3VuZCguLCAyKSA9PSByb3VuZChuZGltcywgMiksICJyZWQiLCAiYmxhY2siKQplbGJvd19wID0gZWxib3dfcCArCiAgZ2dwbG90Mjo6dGhlbWVfY2xhc3NpYygpICsKICBnZ3Bsb3QyOjp0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9IHhfY29sb3IpKQoKZWxib3dfcApgYGAKCldlIGdlbmVyYXRlIGEgdFNORSBhbmQgYSBVTUFQIHdpdGggYHIgbmRpbXNgIHByaW5jaXBhbCBjb21wb25lbnRzIDoKCmBgYHtyIHRzbmVfdW1hcCwgdGltZV9pdCA9IFRSVUV9CnNvYmogPSBTZXVyYXQ6OlJ1blRTTkUoc29iaiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiUk5BX3BjYSIsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IDE6bmRpbXMsCiAgICAgICAgICAgICAgICAgICAgICAgc2VlZC51c2UgPSAxMzM3TCwKICAgICAgICAgICAgICAgICAgICAgICBudW1fdGhyZWFkcyA9IG5fdGhyZWFkcywgIyBSdHNuZTo6UnRzbmUgb3B0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLm5hbWUgPSBwYXN0ZTAoIlJOQV9wY2FfIiwgbmRpbXMsICJfdHNuZSIpKQoKc29iaiA9IFNldXJhdDo6UnVuVU1BUChzb2JqLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJSTkFfcGNhIiwKICAgICAgICAgICAgICAgICAgICAgICBkaW1zID0gMTpuZGltcywKICAgICAgICAgICAgICAgICAgICAgICBzZWVkLnVzZSA9IDEzMzdMLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gcGFzdGUwKCJSTkFfcGNhXyIsIG5kaW1zLCAiX3VtYXAiKSkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHRoZSB0d28gcmVwcmVzZW50YXRpb25zIDoKCmBgYHtyIHNlZV91bWFwX3RzbmUsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdHNuZSA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IHBhc3RlMCgiUk5BX3BjYV8iLCBuZGltcywgIl90c25lIikpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBnZ3Bsb3QyOjpnZ3RpdGxlKCJQQ0EgLSB0U05FIikgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKdW1hcCA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IHBhc3RlMCgiUk5BX3BjYV8iLCBuZGltcywgIl91bWFwIikpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBnZ3Bsb3QyOjpnZ3RpdGxlKCJQQ0EgLSBVTUFQIikgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgp0c25lIHwgdW1hcApgYGAKCiMjIEJhdGNoLWVmZmVjdCBjb3JyZWN0aW9uCgpXZSByZW1vdmUgc2FtcGxlIHNwZWNpZmljIGVmZmVjdCBvbiB0aGUgcGNhIHVzaW5nIEhhcm1vbnkgOgoKYGBge3IgaGFybW9ueSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDUsIHRpbWVfaXQgPSBUUlVFfQpgJXx8JWAgPSBmdW5jdGlvbihsaHMsIHJocykgewogIGlmICghaXMubnVsbCh4ID0gbGhzKSkgewogICAgcmV0dXJuKGxocykKICB9IGVsc2UgewogICAgcmV0dXJuKHJocykKICB9Cn0KCnNldC5zZWVkKDEzMzdMKQpzb2JqID0gaGFybW9ueTo6UnVuSGFybW9ueShvYmplY3QgPSBzb2JqLAogICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieS52YXJzID0gInByb2plY3RfbmFtZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RfY29udmVyZ2VuY2UgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAiUk5BX3BjYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzc2F5LnVzZSA9ICJSTkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24uc2F2ZSA9ICJoYXJtb255IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4Lml0ZXIuaGFybW9ueSA9IDUwLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9qZWN0LmRpbSA9IEZBTFNFKQpgYGAKCkZyb20gdGhpcyBiYXRjaC1lZmZlY3QgcmVtb3ZlZCBwcm9qZWN0aW9uLCB3ZSBnZW5lcmF0ZSBhIHRTTkUgYW5kIGEgVU1BUC4KCmBgYHtyIGhhcm1vbnlfdHNuZV91bWFwLCB0aW1lX2l0ID0gVFJVRX0Kc29iaiA9IFNldXJhdDo6UnVuVU1BUChzb2JqLCAKICAgICAgICAgICAgICAgICAgICAgICBzZWVkLnVzZSA9IDEzMzdMLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXMgPSAxOm5kaW1zLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJoYXJtb255IiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24ubmFtZSA9IHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl91bWFwIiksCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLmtleSA9IHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgInVtYXBfIikpCgpzb2JqID0gU2V1cmF0OjpSdW5UU05FKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgZGltcyA9IDE6bmRpbXMsCiAgICAgICAgICAgICAgICAgICAgICAgc2VlZC51c2UgPSAxMzM3TCwKICAgICAgICAgICAgICAgICAgICAgICBudW1fdGhyZWFkcyA9IG5fdGhyZWFkcywgIyBSdHNuZTo6UnRzbmUgb3B0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gImhhcm1vbnkiLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbi5uYW1lID0gcGFzdGUwKCJoYXJtb255XyIsIG5kaW1zLCAiX3RzbmUiKSwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24ua2V5ID0gcGFzdGUwKCJoYXJtb255IiwgbmRpbXMsICJ0c25lXyIpKQpgYGAKCldlIHZpc3VhbGl6ZSB0aGUgY29ycmVjdGVkIHByb2plY3Rpb25zIDoKCmBgYHtyIHNlZV91bWFwX3RzbmVfYWZ0ZXIsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KdHNuZSA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl90c25lIikpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNhbXBsZV9pbmZvJHByb2plY3RfbmFtZSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKyBnZ3Bsb3QyOjpnZ3RpdGxlKCJQQ0EgLSBoYXJtb255IC0gdFNORSIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnVtYXAgPSBTZXVyYXQ6OkRpbVBsb3Qoc29iaiwgZ3JvdXAuYnkgPSAicHJvamVjdF9uYW1lIiwKICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBwYXN0ZTAoImhhcm1vbnlfIiwgbmRpbXMsICJfdW1hcCIpKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHNhbXBsZV9pbmZvJGNvbG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZSgiUENBIC0gaGFybW9ueSAtIFVNQVAiKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnRzbmUgfCB1bWFwCmBgYAoKV2Ugd2lsbCBrZWVwIHRoZSB0U05FIGZyb20gaGFybW9ueSA6CgpgYGB7ciBzZXRfbmFtZTJEfQpyZWR1Y3Rpb24gPSAiaGFybW9ueSIKbmFtZTJEID0gcGFzdGUwKCJoYXJtb255XyIsIG5kaW1zLCAiX3RzbmUiKQpgYGAKCgojIyBDbHVzdGVyaW5nCgpXZSBnZW5lcmF0ZSBhIGNsdXN0ZXJpbmcgOgoKYGBge3IgY2x1c3RlcmluZywgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDZ9CnNvYmogPSBTZXVyYXQ6OkZpbmROZWlnaGJvcnMoc29iaiwgcmVkdWN0aW9uID0gcmVkdWN0aW9uLCBkaW1zID0gMTpuZGltcykKc29iaiA9IFNldXJhdDo6RmluZENsdXN0ZXJzKHNvYmosIHJlc29sdXRpb24gPSAxLjIpCgpjbHVzdGVyc19wbG90ID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwgbGFiZWwgPSBUUlVFKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArIFNldXJhdDo6Tm9MZWdlbmQoKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDbHVzdGVycyBJRCIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpjbHVzdGVyc19wbG90CmBgYAoKCiMgVmlzdWFsaXphdGlvbgoKV2UgcmVwcmVzZW50IHRoZSA0IHF1YWxpdHkgbWV0cmljcyA6CgpgYGB7ciBxY19wbG90LCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDN9CnBsb3RfbGlzdCA9IFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbWJpbmUgPSBGQUxTRSwgcHQuc2l6ZSA9IDAuMjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJwZXJjZW50Lm10IiwgInBlcmNlbnQucmIiLCAibG9nX25Db3VudF9STkEiLCAibkZlYXR1cmVfUk5BIikpCnBsb3RfbGlzdCA9IGxhcHBseShwbG90X2xpc3QsIEZVTiA9IGZ1bmN0aW9uKG9uZV9wbG90KSB7CiAgb25lX3Bsb3QgKwogICAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3JzID0gYXF1YXJpdXM6Ojpjb2xvcl9nZW5lKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxKQp9KQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbnJvdyA9IDEpCmBgYAoKIyMgQ2VsbCB0eXBlCgpXZSB2aXN1YWxpemUgY2VsbCB0eXBlIDoKCmBgYHtyIHNlZV9jZWxsX3R5cGUsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gOH0KcGxvdF9saXN0ID0gbGFwcGx5KChjKHBhc3RlMCgiUk5BX3BjYV8iLCBuZGltcywgIl90c25lIiksCiAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoIlJOQV9wY2FfIiwgbmRpbXMsICJfdW1hcCIpLAogICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJoYXJtb255XyIsIG5kaW1zLCAiX3RzbmUiKSwKICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgiaGFybW9ueV8iLCBuZGltcywgIl91bWFwIikpKSwKICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKG9uZV9yZWQpIHsKICAgICAgICAgICAgICAgICAgICAgU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImNlbGxfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBvbmVfcmVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29scyA9IGNvbG9yX21hcmtlcnMpICsKICAgICAgICAgICAgICAgICAgICAgICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZShvbmVfcmVkKSArCiAgICAgICAgICAgICAgICAgICAgICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAgICAgICAgICAgICAgICAgIH0pCgpwYXRjaHdvcms6OndyYXBfcGxvdHMocGxvdF9saXN0LCBucm93ID0gMikgKwogIHBhdGNod29yazo6cGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKQpgYGAKCldlIG1ha2UgYSByZXByZXNlbnRhdGlvbiBzcGxpdCBieSBvcmlnaW4gdG8gc2hvdyBjZWxsIHR5cGVzIDoKCmBgYHtyIGNlbGxfdHlwZV9zcGxpdCwgZmlnLndpZHRoID0gMTQsIGZpZy5oZWlnaHQgPSAyMH0KcGxvdF9saXN0ID0gYXF1YXJpdXM6OnBsb3Rfc3BsaXRfZGltcmVkKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdF9ieSA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRfY29sb3IgPSBzZXROYW1lcyhzYW1wbGVfaW5mbyRjb2xvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm0gPSBzYW1wbGVfaW5mbyRwcm9qZWN0X25hbWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgPSAiY2VsbF90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2NvbG9yID0gY29sb3JfbWFya2VycykKCnBsb3RfbGlzdFtbbGVuZ3RoKHBsb3RfbGlzdCkgKyAxXV0gPSBwYXRjaHdvcms6Omd1aWRlX2FyZWEoKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpICsKICBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJgogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKIyMgTGFib3JhdG9yeQoKV2UgY2FuIHJlcHJlc2VudCBjZWxsIHR5cGUgc3BsaXQgYnkgbGFib3JhdG9yeSwgc3BsaXQgYnkgc2FtcGxlIG9mIG9yaWdpbiA6CgpgYGB7ciBwbG90X3NwbGl0X2RpbXJlZF9sYWJvcmF0b3J5LCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDQuNX0KcGxvdF9saXN0ID0gYXF1YXJpdXM6OnBsb3Rfc3BsaXRfZGltcmVkKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdF9ieSA9ICJsYWJvcmF0b3J5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5ID0gImNlbGxfdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9jb2xvciA9IGNvbG9yX21hcmtlcnMpCgpwbG90X2xpc3RbW2xlbmd0aChwbG90X2xpc3QpICsgMV1dID0gcGF0Y2h3b3JrOjpndWlkZV9hcmVhKCkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5yb3cgPSAxKSArCiAgcGF0Y2h3b3JrOjpwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICBnZ3Bsb3QyOjp0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQpgYGAKCiMjIExvY2F0aW9uCgpXZSBjYW4gcmVwcmVzZW50IGNlbGwgdHlwZSBzcGxpdCBieSBsb2NhdGlvbiwgc3BsaXQgYnkgc2FtcGxlIG9mIG9yaWdpbiA6CgpgYGB7ciBwbG90X3NwbGl0X2RpbXJlZF9sb2NhdGlvbiwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA0LjV9CnBsb3RfbGlzdCA9IGFxdWFyaXVzOjpwbG90X3NwbGl0X2RpbXJlZChzb2JqLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRfYnkgPSAibG9jYXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgPSAiY2VsbF90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2NvbG9yID0gY29sb3JfbWFya2VycykKCnBsb3RfbGlzdFtbbGVuZ3RoKHBsb3RfbGlzdCkgKyAxXV0gPSBwYXRjaHdvcms6Omd1aWRlX2FyZWEoKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbnJvdyA9IDEpICsKICBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJgogIGdncGxvdDI6OnRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKIyMgQ2x1c3RlcnMKCldlIGNhbiByZXByZXNlbnQgY2x1c3RlcnMsIHNwbGl0IGJ5IHNhbXBsZSBvZiBvcmlnaW4gOgoKYGBge3IgcGxvdF9zcGxpdF9kaW1yZWRfY2x1c3RlciwgZmlnLndpZHRoID0gMTQsIGZpZy5oZWlnaHQgPSAyMH0KcGxvdF9saXN0ID0gYXF1YXJpdXM6OnBsb3Rfc3BsaXRfZGltcmVkKHNvYmosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdF9ieSA9ICJwcm9qZWN0X25hbWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0X2NvbG9yID0gc2V0TmFtZXMoc2FtcGxlX2luZm8kY29sb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5tID0gc2FtcGxlX2luZm8kcHJvamVjdF9uYW1lKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2NvbG9yID0gYXF1YXJpdXM6OmdnX2NvbG9yX2h1ZShsZW5ndGgobGV2ZWxzKHNvYmokc2V1cmF0X2NsdXN0ZXJzKSkpKQoKcGxvdF9saXN0W1tsZW5ndGgocGxvdF9saXN0KSArIDFdXSA9IGNsdXN0ZXJzX3Bsb3QgKwogIGdncGxvdDI6OmxhYnModGl0bGUgPSAiQ2x1c3RlciBJRCIpICYKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTUpKQoKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHBsb3RfbGlzdCwgbmNvbCA9IDQpICYKICBTZXVyYXQ6Ok5vTGVnZW5kKCkKYGBgCgoKIyMgQ2VsbCBjeWNsZQoKV2UgdmlzdWFsaXplIGNlbGwgY3ljbGUgYW5ub3RhdGlvbiwgYW5kIEJJUkM1IGFuZCBUT1AyQSBleHByZXNzaW9uIGxldmVscyAgOgoKYGBge3IgY2VsbF9jeWNsZSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA4LCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcGxvdF9saXN0ID0gbGlzdCgpCgojIFNldXJhdApwbG90X2xpc3RbWzFdXSA9IFNldXJhdDo6RGltUGxvdChzb2JqLCBncm91cC5ieSA9ICJTZXVyYXQuUGhhc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJTZXVyYXQgYW5ub3RhdGlvbiIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKIyBjeWNsb25lCnBsb3RfbGlzdFtbMl1dID0gU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImN5Y2xvbmUuUGhhc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJjeWNsb25lIGFubm90YXRpb24iKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCiMgQklSQzUKcGxvdF9saXN0W1szXV0gPSBTZXVyYXQ6OkZlYXR1cmVQbG90KHNvYmosIGZlYXR1cmVzID0gIkJJUkM1IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9IG5hbWUyRCkgKwogIGdncGxvdDI6OnNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvcnMgPSBhcXVhcml1czo6Y29sb3JfZ2VuZSkgKwogIFNldXJhdDo6Tm9BeGVzKCkgKwogIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgojIFRLMQpwbG90X2xpc3RbWzRdXSA9IFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSAiVE9QMkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gbmFtZTJEKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG9ycyA9IGFxdWFyaXVzOjpjb2xvcl9nZW5lKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QsIG5jb2wgPSAyKQpgYGAKCiMgU2F2ZQoKV2Ugc2F2ZSB0aGUgU2V1cmF0IG9iamVjdCA6CgpgYGB7ciBzYXZlX3NvYmp9CnNhdmVSRFMoc29iaiwgZmlsZSA9IHBhc3RlMChvdXRfZGlyLCAiLyIsIHNhdmVfbmFtZSwgIl9zb2JqLnJkcyIpKQpgYGAKCgojIFIgU2Vzc2lvbgoKYGBge3Igc2Vzc2lvbmluZm8sIGVjaG8gPSBGQUxTRSwgZm9sZF9vdXRwdXQgPSBUUlVFfQpzZXNzaW9uSW5mbygpCmBgYAoK